<!DOCTYPE html>
<html lang="zh-CN" dir="ltr">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>14.4 布局（Layout）过程 | 落光的Pro博客</title>
    <meta name="description" content="Vite & Vue powered static site generator.">
    <link rel="preload stylesheet" href="./assets/style.4b0df91d.css" as="style">
    
    <script type="module" src="./assets/app.0acbd3fd.js"></script>
    <link rel="preload" href="./assets/inter-roman-latin.2ed14f66.woff2" as="font" type="font/woff2" crossorigin="">
    <link rel="modulepreload" href="./assets/chunks/framework.eaf25f5b.js">
    <link rel="modulepreload" href="./assets/chunks/theme.ce44a0e6.js">
    <link rel="modulepreload" href="./assets/largeFrontEnd_flutter_chapter14_layout.md.0d7a8df3.lean.js">
    <script id="check-dark-light">(()=>{const e=localStorage.getItem("vitepress-theme-appearance")||"auto",a=window.matchMedia("(prefers-color-scheme: dark)").matches;(!e||e==="auto"?a:e==="dark")&&document.documentElement.classList.add("dark")})();</script>
  </head>
  <body>
    <div id="app"><div class="Layout" data-v-1919c326><!--[--><!--]--><!--[--><span tabindex="-1" data-v-0f60ec36></span><a href="#VPContent" class="VPSkipLink visually-hidden" data-v-0f60ec36> Skip to content </a><!--]--><!----><header class="VPNav" data-v-1919c326 data-v-7e5bc4a5><div class="VPNavBar has-sidebar" data-v-7e5bc4a5 data-v-a0fd61f4><div class="container" data-v-a0fd61f4><div class="title" data-v-a0fd61f4><div class="VPNavBarTitle has-sidebar" data-v-a0fd61f4 data-v-86d1bed8><a class="title" href="./" data-v-86d1bed8><!--[--><!--]--><!--[--><img class="VPImage logo" src="./icon-radius.png" alt data-v-8426fc1a><!--]--><!--[-->落光的Pro博客<!--]--><!--[--><!--]--></a></div></div><div class="content" data-v-a0fd61f4><div class="curtain" data-v-a0fd61f4></div><div class="content-body" data-v-a0fd61f4><!--[--><!--]--><div class="VPNavBarSearch search" style="--vp-meta-key:&#39;Meta&#39;;" data-v-a0fd61f4><!--[--><!----><div id="local-search"><button type="button" class="DocSearch DocSearch-Button" aria-label="Search"><span class="DocSearch-Button-Container"><svg class="DocSearch-Search-Icon" width="20" height="20" viewBox="0 0 20 20" aria-label="search icon"><path d="M14.386 14.386l4.0877 4.0877-4.0877-4.0877c-2.9418 2.9419-7.7115 2.9419-10.6533 0-2.9419-2.9418-2.9419-7.7115 0-10.6533 2.9418-2.9419 7.7115-2.9419 10.6533 0 2.9419 2.9418 2.9419 7.7115 0 10.6533z" stroke="currentColor" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"></path></svg><span class="DocSearch-Button-Placeholder">Search</span></span><span class="DocSearch-Button-Keys"><kbd class="DocSearch-Button-Key"></kbd><kbd class="DocSearch-Button-Key">K</kbd></span></button></div><!--]--></div><nav aria-labelledby="main-nav-aria-label" class="VPNavBarMenu menu" data-v-a0fd61f4 data-v-7f418b0f><span id="main-nav-aria-label" class="visually-hidden" data-v-7f418b0f>Main Navigation</span><!--[--><!--[--><div class="VPFlyout VPNavBarMenuGroup" data-v-7f418b0f data-v-9c007e85><button type="button" class="button" aria-haspopup="true" aria-expanded="false" data-v-9c007e85><span class="text" data-v-9c007e85><!----><span data-v-9c007e85>大前端</span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="text-icon" data-v-9c007e85><path d="M12,16c-0.3,0-0.5-0.1-0.7-0.3l-6-6c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l5.3,5.3l5.3-5.3c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-6,6C12.5,15.9,12.3,16,12,16z"></path></svg></span></button><div class="menu" data-v-9c007e85><div class="VPMenu" data-v-9c007e85 data-v-e7ea1737><div class="items" data-v-e7ea1737><!--[--><!--[--><div class="VPMenuLink" data-v-e7ea1737 data-v-43f1e123><a class="VPLink link" href="./largeFrontEnd/webFrontEnd/md/01.html" data-v-43f1e123><!--[-->Web前端<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-e7ea1737 data-v-43f1e123><a class="VPLink link" href="./largeFrontEnd/uniapp/" data-v-43f1e123><!--[-->Uni-app<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-e7ea1737 data-v-43f1e123><a class="VPLink link" href="./largeFrontEnd/weChatMiniProgram/" data-v-43f1e123><!--[-->微信小程序<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-e7ea1737 data-v-43f1e123><a class="VPLink link" href="./largeFrontEnd/electron/" data-v-43f1e123><!--[-->Electron<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-e7ea1737 data-v-43f1e123><a class="VPLink link" href="./largeFrontEnd/flutter/" data-v-43f1e123><!--[-->Flutter<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-e7ea1737 data-v-43f1e123><a class="VPLink link" href="./largeFrontEnd/interview/01.html" data-v-43f1e123><!--[-->前端面试题<!--]--></a></div><!--]--><!--]--></div><!--[--><!--]--></div></div></div><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="./backEnd/" tabindex="0" data-v-7f418b0f data-v-42ef59de><!--[--><span data-v-42ef59de>后端</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="./linux/" tabindex="0" data-v-7f418b0f data-v-42ef59de><!--[--><span data-v-42ef59de>Linux</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="./myTeam/" tabindex="0" data-v-7f418b0f data-v-42ef59de><!--[--><span data-v-42ef59de>我的团队</span><!--]--></a><!--]--><!--]--></nav><!----><div class="VPNavBarAppearance appearance" data-v-a0fd61f4 data-v-f6a63727><button class="VPSwitch VPSwitchAppearance" type="button" role="switch" title="toggle dark mode" aria-checked="false" data-v-f6a63727 data-v-ce54a7d1 data-v-b1685198><span class="check" data-v-b1685198><span class="icon" data-v-b1685198><!--[--><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="sun" data-v-ce54a7d1><path d="M12,18c-3.3,0-6-2.7-6-6s2.7-6,6-6s6,2.7,6,6S15.3,18,12,18zM12,8c-2.2,0-4,1.8-4,4c0,2.2,1.8,4,4,4c2.2,0,4-1.8,4-4C16,9.8,14.2,8,12,8z"></path><path d="M12,4c-0.6,0-1-0.4-1-1V1c0-0.6,0.4-1,1-1s1,0.4,1,1v2C13,3.6,12.6,4,12,4z"></path><path d="M12,24c-0.6,0-1-0.4-1-1v-2c0-0.6,0.4-1,1-1s1,0.4,1,1v2C13,23.6,12.6,24,12,24z"></path><path d="M5.6,6.6c-0.3,0-0.5-0.1-0.7-0.3L3.5,4.9c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l1.4,1.4c0.4,0.4,0.4,1,0,1.4C6.2,6.5,5.9,6.6,5.6,6.6z"></path><path d="M19.8,20.8c-0.3,0-0.5-0.1-0.7-0.3l-1.4-1.4c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l1.4,1.4c0.4,0.4,0.4,1,0,1.4C20.3,20.7,20,20.8,19.8,20.8z"></path><path d="M3,13H1c-0.6,0-1-0.4-1-1s0.4-1,1-1h2c0.6,0,1,0.4,1,1S3.6,13,3,13z"></path><path d="M23,13h-2c-0.6,0-1-0.4-1-1s0.4-1,1-1h2c0.6,0,1,0.4,1,1S23.6,13,23,13z"></path><path d="M4.2,20.8c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l1.4-1.4c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-1.4,1.4C4.7,20.7,4.5,20.8,4.2,20.8z"></path><path d="M18.4,6.6c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l1.4-1.4c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-1.4,1.4C18.9,6.5,18.6,6.6,18.4,6.6z"></path></svg><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="moon" data-v-ce54a7d1><path d="M12.1,22c-0.3,0-0.6,0-0.9,0c-5.5-0.5-9.5-5.4-9-10.9c0.4-4.8,4.2-8.6,9-9c0.4,0,0.8,0.2,1,0.5c0.2,0.3,0.2,0.8-0.1,1.1c-2,2.7-1.4,6.4,1.3,8.4c2.1,1.6,5,1.6,7.1,0c0.3-0.2,0.7-0.3,1.1-0.1c0.3,0.2,0.5,0.6,0.5,1c-0.2,2.7-1.5,5.1-3.6,6.8C16.6,21.2,14.4,22,12.1,22zM9.3,4.4c-2.9,1-5,3.6-5.2,6.8c-0.4,4.4,2.8,8.3,7.2,8.7c2.1,0.2,4.2-0.4,5.8-1.8c1.1-0.9,1.9-2.1,2.4-3.4c-2.5,0.9-5.3,0.5-7.5-1.1C9.2,11.4,8.1,7.7,9.3,4.4z"></path></svg><!--]--></span></span></button></div><div class="VPSocialLinks VPNavBarSocialLinks social-links" data-v-a0fd61f4 data-v-0394ad82 data-v-7bc22406><!--[--><a class="VPSocialLink no-icon" href="https://gitee.com/luoguangguang" aria-label="github" target="_blank" rel="noopener" data-v-7bc22406 data-v-f80f8133><svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>GitHub</title><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg></a><a class="VPSocialLink no-icon" href="..." aria-label="cool link" target="_blank" rel="noopener" data-v-7bc22406 data-v-f80f8133><svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Dribbble</title><path d="M12...6.38z"/></svg></a><!--]--></div><div class="VPFlyout VPNavBarExtra extra" data-v-a0fd61f4 data-v-40855f84 data-v-9c007e85><button type="button" class="button" aria-haspopup="true" aria-expanded="false" aria-label="extra navigation" data-v-9c007e85><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="icon" data-v-9c007e85><circle cx="12" cy="12" r="2"></circle><circle cx="19" cy="12" r="2"></circle><circle cx="5" cy="12" r="2"></circle></svg></button><div class="menu" data-v-9c007e85><div class="VPMenu" data-v-9c007e85 data-v-e7ea1737><!----><!--[--><!--[--><!----><div class="group" data-v-40855f84><div class="item appearance" data-v-40855f84><p class="label" data-v-40855f84>Appearance</p><div class="appearance-action" data-v-40855f84><button class="VPSwitch VPSwitchAppearance" type="button" role="switch" title="toggle dark mode" aria-checked="false" data-v-40855f84 data-v-ce54a7d1 data-v-b1685198><span class="check" data-v-b1685198><span class="icon" data-v-b1685198><!--[--><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="sun" data-v-ce54a7d1><path d="M12,18c-3.3,0-6-2.7-6-6s2.7-6,6-6s6,2.7,6,6S15.3,18,12,18zM12,8c-2.2,0-4,1.8-4,4c0,2.2,1.8,4,4,4c2.2,0,4-1.8,4-4C16,9.8,14.2,8,12,8z"></path><path d="M12,4c-0.6,0-1-0.4-1-1V1c0-0.6,0.4-1,1-1s1,0.4,1,1v2C13,3.6,12.6,4,12,4z"></path><path d="M12,24c-0.6,0-1-0.4-1-1v-2c0-0.6,0.4-1,1-1s1,0.4,1,1v2C13,23.6,12.6,24,12,24z"></path><path d="M5.6,6.6c-0.3,0-0.5-0.1-0.7-0.3L3.5,4.9c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l1.4,1.4c0.4,0.4,0.4,1,0,1.4C6.2,6.5,5.9,6.6,5.6,6.6z"></path><path d="M19.8,20.8c-0.3,0-0.5-0.1-0.7-0.3l-1.4-1.4c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l1.4,1.4c0.4,0.4,0.4,1,0,1.4C20.3,20.7,20,20.8,19.8,20.8z"></path><path d="M3,13H1c-0.6,0-1-0.4-1-1s0.4-1,1-1h2c0.6,0,1,0.4,1,1S3.6,13,3,13z"></path><path d="M23,13h-2c-0.6,0-1-0.4-1-1s0.4-1,1-1h2c0.6,0,1,0.4,1,1S23.6,13,23,13z"></path><path d="M4.2,20.8c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l1.4-1.4c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-1.4,1.4C4.7,20.7,4.5,20.8,4.2,20.8z"></path><path d="M18.4,6.6c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l1.4-1.4c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-1.4,1.4C18.9,6.5,18.6,6.6,18.4,6.6z"></path></svg><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="moon" data-v-ce54a7d1><path d="M12.1,22c-0.3,0-0.6,0-0.9,0c-5.5-0.5-9.5-5.4-9-10.9c0.4-4.8,4.2-8.6,9-9c0.4,0,0.8,0.2,1,0.5c0.2,0.3,0.2,0.8-0.1,1.1c-2,2.7-1.4,6.4,1.3,8.4c2.1,1.6,5,1.6,7.1,0c0.3-0.2,0.7-0.3,1.1-0.1c0.3,0.2,0.5,0.6,0.5,1c-0.2,2.7-1.5,5.1-3.6,6.8C16.6,21.2,14.4,22,12.1,22zM9.3,4.4c-2.9,1-5,3.6-5.2,6.8c-0.4,4.4,2.8,8.3,7.2,8.7c2.1,0.2,4.2-0.4,5.8-1.8c1.1-0.9,1.9-2.1,2.4-3.4c-2.5,0.9-5.3,0.5-7.5-1.1C9.2,11.4,8.1,7.7,9.3,4.4z"></path></svg><!--]--></span></span></button></div></div></div><div class="group" data-v-40855f84><div class="item social-links" data-v-40855f84><div class="VPSocialLinks social-links-list" data-v-40855f84 data-v-7bc22406><!--[--><a class="VPSocialLink no-icon" href="https://gitee.com/luoguangguang" aria-label="github" target="_blank" rel="noopener" data-v-7bc22406 data-v-f80f8133><svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>GitHub</title><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg></a><a class="VPSocialLink no-icon" href="..." aria-label="cool link" target="_blank" rel="noopener" data-v-7bc22406 data-v-f80f8133><svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Dribbble</title><path d="M12...6.38z"/></svg></a><!--]--></div></div></div><!--]--><!--]--></div></div></div><!--[--><!--]--><button type="button" class="VPNavBarHamburger hamburger" aria-label="mobile navigation" aria-expanded="false" aria-controls="VPNavScreen" data-v-a0fd61f4 data-v-e5dd9c1c><span class="container" data-v-e5dd9c1c><span class="top" data-v-e5dd9c1c></span><span class="middle" data-v-e5dd9c1c></span><span class="bottom" data-v-e5dd9c1c></span></span></button></div></div></div></div><!----></header><div class="VPLocalNav reached-top" data-v-1919c326 data-v-79c8c1df><button class="menu" aria-expanded="false" aria-controls="VPSidebarNav" data-v-79c8c1df><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="menu-icon" data-v-79c8c1df><path d="M17,11H3c-0.6,0-1-0.4-1-1s0.4-1,1-1h14c0.6,0,1,0.4,1,1S17.6,11,17,11z"></path><path d="M21,7H3C2.4,7,2,6.6,2,6s0.4-1,1-1h18c0.6,0,1,0.4,1,1S21.6,7,21,7z"></path><path d="M21,15H3c-0.6,0-1-0.4-1-1s0.4-1,1-1h18c0.6,0,1,0.4,1,1S21.6,15,21,15z"></path><path d="M17,19H3c-0.6,0-1-0.4-1-1s0.4-1,1-1h14c0.6,0,1,0.4,1,1S17.6,19,17,19z"></path></svg><span class="menu-text" data-v-79c8c1df>Menu</span></button><div class="VPLocalNavOutlineDropdown" style="--vp-vh:0px;" data-v-79c8c1df data-v-1c15a60a><button data-v-1c15a60a>Return to top</button><!----></div></div><aside class="VPSidebar" data-v-1919c326 data-v-b00e2fdd><div class="curtain" data-v-b00e2fdd></div><nav class="nav" id="VPSidebarNav" aria-labelledby="sidebar-aria-label" tabindex="-1" data-v-b00e2fdd><span class="visually-hidden" id="sidebar-aria-label" data-v-b00e2fdd> Sidebar Navigation </span><!--[--><!--]--><!--[--><div class="group" data-v-b00e2fdd><section class="VPSidebarItem level-0 collapsible" data-v-b00e2fdd data-v-e31bd47b><div class="item" role="button" tabindex="0" data-v-e31bd47b><div class="indicator" data-v-e31bd47b></div><h2 class="text" data-v-e31bd47b>Flutter入门到实战</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-e31bd47b><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-e31bd47b><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-e31bd47b><!--[--><div class="VPSidebarItem level-1 is-link" data-v-e31bd47b data-v-e31bd47b><div class="item" data-v-e31bd47b><div class="indicator" data-v-e31bd47b></div><a class="VPLink link link" href="./largeFrontEnd/flutter/chapter1/index.html" data-v-e31bd47b><!--[--><p class="text" data-v-e31bd47b>第一章 初识Flutter</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-e31bd47b data-v-e31bd47b><div class="item" data-v-e31bd47b><div class="indicator" data-v-e31bd47b></div><a class="VPLink link link" href="./largeFrontEnd/flutter/chapter2/index.html" data-v-e31bd47b><!--[--><p class="text" data-v-e31bd47b>第二章 简介</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-e31bd47b data-v-e31bd47b><div class="item" data-v-e31bd47b><div class="indicator" data-v-e31bd47b></div><a class="VPLink link link" href="./largeFrontEnd/flutter/chapter3/index.html" data-v-e31bd47b><!--[--><p class="text" data-v-e31bd47b>第三章 基础组件</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-e31bd47b data-v-e31bd47b><div class="item" data-v-e31bd47b><div class="indicator" data-v-e31bd47b></div><a class="VPLink link link" href="./largeFrontEnd/flutter/chapter4/index.html" data-v-e31bd47b><!--[--><p class="text" data-v-e31bd47b>第四章 布局类组件</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-e31bd47b data-v-e31bd47b><div class="item" data-v-e31bd47b><div class="indicator" data-v-e31bd47b></div><a class="VPLink link link" href="./largeFrontEnd/flutter/chapter5/index.html" data-v-e31bd47b><!--[--><p class="text" data-v-e31bd47b>第五章 容器类Widget</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-e31bd47b data-v-e31bd47b><div class="item" data-v-e31bd47b><div class="indicator" data-v-e31bd47b></div><a class="VPLink link link" href="./largeFrontEnd/flutter/chapter6/index.html" data-v-e31bd47b><!--[--><p class="text" data-v-e31bd47b>第六章 可滚动组件</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-e31bd47b data-v-e31bd47b><div class="item" data-v-e31bd47b><div class="indicator" data-v-e31bd47b></div><a class="VPLink link link" href="./largeFrontEnd/flutter/chapter7/index.html" data-v-e31bd47b><!--[--><p class="text" data-v-e31bd47b>第七章 功能型Widget简介</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-e31bd47b data-v-e31bd47b><div class="item" data-v-e31bd47b><div class="indicator" data-v-e31bd47b></div><a class="VPLink link link" href="./largeFrontEnd/flutter/chapter8/index.html" data-v-e31bd47b><!--[--><p class="text" data-v-e31bd47b>第八章 事件处理与通知</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-e31bd47b data-v-e31bd47b><div class="item" data-v-e31bd47b><div class="indicator" data-v-e31bd47b></div><a class="VPLink link link" href="./largeFrontEnd/flutter/chapter9/index.html" data-v-e31bd47b><!--[--><p class="text" data-v-e31bd47b>第九章 动画</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-e31bd47b data-v-e31bd47b><div class="item" data-v-e31bd47b><div class="indicator" data-v-e31bd47b></div><a class="VPLink link link" href="./largeFrontEnd/flutter/chapter10/index.html" data-v-e31bd47b><!--[--><p class="text" data-v-e31bd47b>第十章 自定义组件</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-e31bd47b data-v-e31bd47b><div class="item" data-v-e31bd47b><div class="indicator" data-v-e31bd47b></div><a class="VPLink link link" href="./largeFrontEnd/flutter/chapter11/index.html" data-v-e31bd47b><!--[--><p class="text" data-v-e31bd47b>第十一章 文件操作</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-e31bd47b data-v-e31bd47b><div class="item" data-v-e31bd47b><div class="indicator" data-v-e31bd47b></div><a class="VPLink link link" href="./largeFrontEnd/flutter/chapter12/index.html" data-v-e31bd47b><!--[--><p class="text" data-v-e31bd47b>第十二章 Flutter 扩展</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-e31bd47b data-v-e31bd47b><div class="item" data-v-e31bd47b><div class="indicator" data-v-e31bd47b></div><a class="VPLink link link" href="./largeFrontEnd/flutter/chapter13/index.html" data-v-e31bd47b><!--[--><p class="text" data-v-e31bd47b>第十三章 多语言</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-e31bd47b data-v-e31bd47b><div class="item" data-v-e31bd47b><div class="indicator" data-v-e31bd47b></div><a class="VPLink link link" href="./largeFrontEnd/flutter/chapter14/index.html" data-v-e31bd47b><!--[--><p class="text" data-v-e31bd47b>第十四章 高级进阶</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><!--]--><!--[--><!--]--></nav></aside><div class="VPContent has-sidebar" id="VPContent" data-v-1919c326 data-v-669faec9><div class="VPDoc has-sidebar has-aside" data-v-669faec9 data-v-6b87e69f><!--[--><!--]--><div class="container" data-v-6b87e69f><div class="aside" data-v-6b87e69f><div class="aside-curtain" data-v-6b87e69f></div><div class="aside-container" data-v-6b87e69f><div class="aside-content" data-v-6b87e69f><div class="VPDocAside" data-v-6b87e69f data-v-3f215769><!--[--><!--]--><!--[--><!--]--><div class="VPDocAsideOutline" role="navigation" data-v-3f215769 data-v-6ae8e080><div class="content" data-v-6ae8e080><div class="outline-marker" data-v-6ae8e080></div><div class="outline-title" role="heading" data-v-6ae8e080>On this page</div><nav aria-labelledby="doc-outline-aria-label" data-v-6ae8e080><span class="visually-hidden" id="doc-outline-aria-label" data-v-6ae8e080> Table of Contents for current page </span><ul class="root" data-v-6ae8e080 data-v-d0ee3533><!--[--><!--]--></ul></nav></div></div><!--[--><!--]--><div class="spacer" data-v-3f215769></div><!--[--><!--]--><div class="VPDocAsideCarbonAds" data-v-3f215769><div class="VPCarbonAds" data-v-2e1efd59></div></div><!--[--><!--]--><!--[--><!--]--></div></div></div></div><div class="content" data-v-6b87e69f><div class="content-container" data-v-6b87e69f><!--[--><!--]--><!----><main class="main" data-v-6b87e69f><div style="position:relative;" class="vp-doc _largeFrontEnd_flutter_chapter14_layout" data-v-6b87e69f><div><h1 id="_14-4-布局-layout-过程" tabindex="-1">14.4 布局（Layout）过程 <a class="header-anchor" href="#_14-4-布局-layout-过程" aria-label="Permalink to &quot;14.4 布局（Layout）过程&quot;">​</a></h1><p>Layout（布局）过程主要是确定每一个组件的布局信息（大小和位置），Flutter 的布局过程如下：</p><ol><li>父节点向子节点传递约束（constraints）信息，限制子节点的最大和最小宽高。</li><li>子节点根据约束信息确定自己的大小（size）。</li><li>父节点根据特定布局规则（不同布局组件会有不同的布局算法）确定每一个子节点在父节点布局空间中的位置，用偏移 offset 表示。</li><li>递归整个过程，确定出每一个节点的大小和位置。</li></ol><p>可以看到，组件的大小是由自身决定的，而组件的位置是由父组件决定的。</p><p>Flutter 中的布局类组件很多，根据孩子数量可以分为单子组件和多子组件，下面我们先通过分别自定义一个单子组件和多子组件来直观理解一下Flutter的布局过程，之后会介绍一下布局更新过程和 Flutter 中的 Constraints（约束）。</p><h2 id="_14-4-1-单子组件布局示例-customcenter" tabindex="-1">14.4.1 单子组件布局示例（CustomCenter） <a class="header-anchor" href="#_14-4-1-单子组件布局示例-customcenter" aria-label="Permalink to &quot;14.4.1 单子组件布局示例（CustomCenter）&quot;">​</a></h2><p>我们实现一个单子组件 CustomCenter，功能基本和 Center 组件对齐，通过这个实例我们演示一下布局的主要流程。</p><p>首先，我们定义组件，为了介绍布局原理，我们不采用组合的方式来实现组件，而是直接通过定制 RenderObject 的方式来实现。因为居中组件需要包含一个子节点，所以我们直接继承 SingleChildRenderObjectWidget。</p><div class="language-dart vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre class="shiki github-dark vp-code-dark"><code><span class="line"><span style="color:#F97583;">class</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">CustomCenter</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">extends</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">SingleChildRenderObjectWidget</span><span style="color:#E1E4E8;"> {</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">const</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">CustomCenter2</span><span style="color:#E1E4E8;">({</span><span style="color:#79B8FF;">Key</span><span style="color:#F97583;">?</span><span style="color:#E1E4E8;"> key, </span><span style="color:#F97583;">required</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Widget</span><span style="color:#E1E4E8;"> child})</span></span>
<span class="line"><span style="color:#E1E4E8;">      </span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">super</span><span style="color:#E1E4E8;">(key</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> key, child</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> child);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">@override</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">RenderObject</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">createRenderObject</span><span style="color:#E1E4E8;">(</span><span style="color:#79B8FF;">BuildContext</span><span style="color:#E1E4E8;"> context) {</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">return</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">RenderCustomCenter</span><span style="color:#E1E4E8;">();</span></span>
<span class="line"><span style="color:#E1E4E8;">  }</span></span>
<span class="line"><span style="color:#E1E4E8;">}</span></span></code></pre><pre class="shiki github-light vp-code-light"><code><span class="line"><span style="color:#D73A49;">class</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">CustomCenter</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">extends</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">SingleChildRenderObjectWidget</span><span style="color:#24292E;"> {</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">const</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">CustomCenter2</span><span style="color:#24292E;">({</span><span style="color:#005CC5;">Key</span><span style="color:#D73A49;">?</span><span style="color:#24292E;"> key, </span><span style="color:#D73A49;">required</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Widget</span><span style="color:#24292E;"> child})</span></span>
<span class="line"><span style="color:#24292E;">      </span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">super</span><span style="color:#24292E;">(key</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> key, child</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> child);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">@override</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">RenderObject</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">createRenderObject</span><span style="color:#24292E;">(</span><span style="color:#005CC5;">BuildContext</span><span style="color:#24292E;"> context) {</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">return</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">RenderCustomCenter</span><span style="color:#24292E;">();</span></span>
<span class="line"><span style="color:#24292E;">  }</span></span>
<span class="line"><span style="color:#24292E;">}</span></span></code></pre></div><p>接着实现 RenderCustomCenter。这里直接继承 RenderObject 会更接近底层一点，但这需要我们自己手动实现一些和布局无关的东西，比如事件分发等逻辑。为了更聚焦布局本身，我们选择继承自RenderShiftedBox，它会帮我们实现布局之外的一些功能，这样我们只需要重写<code>performLayout</code>，在该函数中实现子节点居中算法即可。</p><div class="language-dart vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre class="shiki github-dark vp-code-dark"><code><span class="line"><span style="color:#F97583;">class</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">RenderCustomCenter</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">extends</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">RenderShiftedBox</span><span style="color:#E1E4E8;"> {</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">RenderCustomCenter</span><span style="color:#E1E4E8;">({</span><span style="color:#79B8FF;">RenderBox</span><span style="color:#F97583;">?</span><span style="color:#E1E4E8;"> child}) </span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">super</span><span style="color:#E1E4E8;">(child);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">@override</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">void</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">performLayout</span><span style="color:#E1E4E8;">() {</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">//1. 先对子组件进行layout，随后获取它的size</span></span>
<span class="line"><span style="color:#E1E4E8;">    child</span><span style="color:#F97583;">!</span><span style="color:#E1E4E8;">.</span><span style="color:#B392F0;">layout</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">      constraints.</span><span style="color:#B392F0;">loosen</span><span style="color:#E1E4E8;">(), </span><span style="color:#6A737D;">//将约束传递给子节点</span></span>
<span class="line"><span style="color:#E1E4E8;">      parentUsesSize</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">true</span><span style="color:#E1E4E8;">, </span><span style="color:#6A737D;">// 因为我们接下来要使用child的size,所以不能为false</span></span>
<span class="line"><span style="color:#E1E4E8;">    );</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">//2.根据子组件的大小确定自身的大小</span></span>
<span class="line"><span style="color:#E1E4E8;">    size </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> constraints.</span><span style="color:#B392F0;">constrain</span><span style="color:#E1E4E8;">(</span><span style="color:#79B8FF;">Size</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">      constraints.maxWidth </span><span style="color:#F97583;">==</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">double</span><span style="color:#E1E4E8;">.infinity</span></span>
<span class="line"><span style="color:#E1E4E8;">          </span><span style="color:#F97583;">?</span><span style="color:#E1E4E8;"> child</span><span style="color:#F97583;">!</span><span style="color:#E1E4E8;">.size.width</span></span>
<span class="line"><span style="color:#E1E4E8;">          </span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">double</span><span style="color:#E1E4E8;">.infinity,</span></span>
<span class="line"><span style="color:#E1E4E8;">      constraints.maxHeight </span><span style="color:#F97583;">==</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">double</span><span style="color:#E1E4E8;">.infinity</span></span>
<span class="line"><span style="color:#E1E4E8;">          </span><span style="color:#F97583;">?</span><span style="color:#E1E4E8;"> child</span><span style="color:#F97583;">!</span><span style="color:#E1E4E8;">.size.height</span></span>
<span class="line"><span style="color:#E1E4E8;">          </span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">double</span><span style="color:#E1E4E8;">.infinity,</span></span>
<span class="line"><span style="color:#E1E4E8;">    ));</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">// 3. 根据父节点子节点的大小，算出子节点在父节点中居中之后的偏移，然后将这个偏移保存在</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">// 子节点的parentData中，在后续的绘制阶段，会用到。</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#79B8FF;">BoxParentData</span><span style="color:#E1E4E8;"> parentData </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> child</span><span style="color:#F97583;">!</span><span style="color:#E1E4E8;">.parentData </span><span style="color:#F97583;">as</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">BoxParentData</span><span style="color:#E1E4E8;">;</span></span>
<span class="line"><span style="color:#E1E4E8;">    parentData.offset </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> ((size </span><span style="color:#F97583;">-</span><span style="color:#E1E4E8;"> child</span><span style="color:#F97583;">!</span><span style="color:#E1E4E8;">.size) </span><span style="color:#F97583;">as</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Offset</span><span style="color:#E1E4E8;">) </span><span style="color:#F97583;">/</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">2</span><span style="color:#E1E4E8;">;</span></span>
<span class="line"><span style="color:#E1E4E8;">  }</span></span>
<span class="line"><span style="color:#E1E4E8;">}</span></span></code></pre><pre class="shiki github-light vp-code-light"><code><span class="line"><span style="color:#D73A49;">class</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">RenderCustomCenter</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">extends</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">RenderShiftedBox</span><span style="color:#24292E;"> {</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">RenderCustomCenter</span><span style="color:#24292E;">({</span><span style="color:#005CC5;">RenderBox</span><span style="color:#D73A49;">?</span><span style="color:#24292E;"> child}) </span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">super</span><span style="color:#24292E;">(child);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">@override</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">void</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">performLayout</span><span style="color:#24292E;">() {</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">//1. 先对子组件进行layout，随后获取它的size</span></span>
<span class="line"><span style="color:#24292E;">    child</span><span style="color:#D73A49;">!</span><span style="color:#24292E;">.</span><span style="color:#6F42C1;">layout</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">      constraints.</span><span style="color:#6F42C1;">loosen</span><span style="color:#24292E;">(), </span><span style="color:#6A737D;">//将约束传递给子节点</span></span>
<span class="line"><span style="color:#24292E;">      parentUsesSize</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">true</span><span style="color:#24292E;">, </span><span style="color:#6A737D;">// 因为我们接下来要使用child的size,所以不能为false</span></span>
<span class="line"><span style="color:#24292E;">    );</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">//2.根据子组件的大小确定自身的大小</span></span>
<span class="line"><span style="color:#24292E;">    size </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> constraints.</span><span style="color:#6F42C1;">constrain</span><span style="color:#24292E;">(</span><span style="color:#005CC5;">Size</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">      constraints.maxWidth </span><span style="color:#D73A49;">==</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">double</span><span style="color:#24292E;">.infinity</span></span>
<span class="line"><span style="color:#24292E;">          </span><span style="color:#D73A49;">?</span><span style="color:#24292E;"> child</span><span style="color:#D73A49;">!</span><span style="color:#24292E;">.size.width</span></span>
<span class="line"><span style="color:#24292E;">          </span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">double</span><span style="color:#24292E;">.infinity,</span></span>
<span class="line"><span style="color:#24292E;">      constraints.maxHeight </span><span style="color:#D73A49;">==</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">double</span><span style="color:#24292E;">.infinity</span></span>
<span class="line"><span style="color:#24292E;">          </span><span style="color:#D73A49;">?</span><span style="color:#24292E;"> child</span><span style="color:#D73A49;">!</span><span style="color:#24292E;">.size.height</span></span>
<span class="line"><span style="color:#24292E;">          </span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">double</span><span style="color:#24292E;">.infinity,</span></span>
<span class="line"><span style="color:#24292E;">    ));</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">// 3. 根据父节点子节点的大小，算出子节点在父节点中居中之后的偏移，然后将这个偏移保存在</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">// 子节点的parentData中，在后续的绘制阶段，会用到。</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#005CC5;">BoxParentData</span><span style="color:#24292E;"> parentData </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> child</span><span style="color:#D73A49;">!</span><span style="color:#24292E;">.parentData </span><span style="color:#D73A49;">as</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">BoxParentData</span><span style="color:#24292E;">;</span></span>
<span class="line"><span style="color:#24292E;">    parentData.offset </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> ((size </span><span style="color:#D73A49;">-</span><span style="color:#24292E;"> child</span><span style="color:#D73A49;">!</span><span style="color:#24292E;">.size) </span><span style="color:#D73A49;">as</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Offset</span><span style="color:#24292E;">) </span><span style="color:#D73A49;">/</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">2</span><span style="color:#24292E;">;</span></span>
<span class="line"><span style="color:#24292E;">  }</span></span>
<span class="line"><span style="color:#24292E;">}</span></span></code></pre></div><p>布局过程请参考注释，在此需要额外说明有3点：</p><ol><li><p>在对子节点进行布局时， <code>constraints </code> 是 CustomCenter 的父组件传递给自己的约束信息，我们传递给子节点的约束信息是<code>constraints.loosen()</code>，下面看一下loosen的实现源码：</p><div class="language-dart vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre class="shiki github-dark vp-code-dark"><code><span class="line"><span style="color:#79B8FF;">BoxConstraints</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">loosen</span><span style="color:#E1E4E8;">() {</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">return</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">BoxConstraints</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">    minWidth</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">0.0</span><span style="color:#E1E4E8;">,</span></span>
<span class="line"><span style="color:#E1E4E8;">    maxWidth</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> maxWidth,</span></span>
<span class="line"><span style="color:#E1E4E8;">    minHeight</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">0.0</span><span style="color:#E1E4E8;">,</span></span>
<span class="line"><span style="color:#E1E4E8;">    maxHeight</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> maxHeight,</span></span>
<span class="line"><span style="color:#E1E4E8;">  );</span></span>
<span class="line"><span style="color:#E1E4E8;">}</span></span></code></pre><pre class="shiki github-light vp-code-light"><code><span class="line"><span style="color:#005CC5;">BoxConstraints</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">loosen</span><span style="color:#24292E;">() {</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">return</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">BoxConstraints</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">    minWidth</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">0.0</span><span style="color:#24292E;">,</span></span>
<span class="line"><span style="color:#24292E;">    maxWidth</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> maxWidth,</span></span>
<span class="line"><span style="color:#24292E;">    minHeight</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">0.0</span><span style="color:#24292E;">,</span></span>
<span class="line"><span style="color:#24292E;">    maxHeight</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> maxHeight,</span></span>
<span class="line"><span style="color:#24292E;">  );</span></span>
<span class="line"><span style="color:#24292E;">}</span></span></code></pre></div><p>很明显，CustomCenter 约束子节点最大宽高不超过自身的最大宽高。</p></li><li><p>子节点在父节点（CustomCenter）的约束下，确定自己的宽高；此时CustomCenter会根据子节点的宽高确定自己的宽高，上面代码的逻辑是，如果CustomCenter父节点传递给它最大宽高约束是无限大时，它的宽高会设置为它子节点的宽高。注意，如果这时将CustomCenter的宽高也设置为无限大就会有问题，因为在一个无限大的范围内自己的宽高也是无限大的话，那么实际上的宽高到底是多大，它的父节点会懵逼的！屏幕的大小是固定的，这显然不合理。如果CustomCenter父节点传递给它的最大宽高约束不是无限大，那么是可以指定自己的宽高为无限大的，因为在一个有限的空间内，子节点如果说自己无限大，那么最大也就是父节点的大小。所以，简而言之，CustomCenter 会尽可能让自己填满父元素的空间。</p></li><li><p>CustomCenter 确定了自己的大小和子节点大小之后就可以确定子节点的位置了，根据居中算法，将子节点的原点坐标算出后保存在子节点的 parentData 中，在后续的绘制阶段会用到，具体怎么用，我们看一下RenderShiftedBox中默认的 paint 实现：</p><div class="language-dart vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre class="shiki github-dark vp-code-dark"><code><span class="line"><span style="color:#F97583;">@override</span></span>
<span class="line"><span style="color:#F97583;">void</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">paint</span><span style="color:#E1E4E8;">(</span><span style="color:#79B8FF;">PaintingContext</span><span style="color:#E1E4E8;"> context, </span><span style="color:#79B8FF;">Offset</span><span style="color:#E1E4E8;"> offset) {</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">if</span><span style="color:#E1E4E8;"> (child </span><span style="color:#F97583;">!=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">null</span><span style="color:#E1E4E8;">) {</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">final</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">BoxParentData</span><span style="color:#E1E4E8;"> childParentData </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> child</span><span style="color:#F97583;">!</span><span style="color:#E1E4E8;">.parentData</span><span style="color:#F97583;">!</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">as</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">BoxParentData</span><span style="color:#E1E4E8;">;</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">//从child.parentData中取出子节点相对当前节点的偏移，加上当前节点在屏幕中的偏移，</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">//便是子节点在屏幕中的偏移。</span></span>
<span class="line"><span style="color:#E1E4E8;">    context.</span><span style="color:#B392F0;">paintChild</span><span style="color:#E1E4E8;">(child</span><span style="color:#F97583;">!</span><span style="color:#E1E4E8;">, childParentData.offset </span><span style="color:#F97583;">+</span><span style="color:#E1E4E8;"> offset);</span></span>
<span class="line"><span style="color:#E1E4E8;">  }</span></span>
<span class="line"><span style="color:#E1E4E8;">}</span></span></code></pre><pre class="shiki github-light vp-code-light"><code><span class="line"><span style="color:#D73A49;">@override</span></span>
<span class="line"><span style="color:#D73A49;">void</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">paint</span><span style="color:#24292E;">(</span><span style="color:#005CC5;">PaintingContext</span><span style="color:#24292E;"> context, </span><span style="color:#005CC5;">Offset</span><span style="color:#24292E;"> offset) {</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">if</span><span style="color:#24292E;"> (child </span><span style="color:#D73A49;">!=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">null</span><span style="color:#24292E;">) {</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">final</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">BoxParentData</span><span style="color:#24292E;"> childParentData </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> child</span><span style="color:#D73A49;">!</span><span style="color:#24292E;">.parentData</span><span style="color:#D73A49;">!</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">as</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">BoxParentData</span><span style="color:#24292E;">;</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">//从child.parentData中取出子节点相对当前节点的偏移，加上当前节点在屏幕中的偏移，</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">//便是子节点在屏幕中的偏移。</span></span>
<span class="line"><span style="color:#24292E;">    context.</span><span style="color:#6F42C1;">paintChild</span><span style="color:#24292E;">(child</span><span style="color:#D73A49;">!</span><span style="color:#24292E;">, childParentData.offset </span><span style="color:#D73A49;">+</span><span style="color:#24292E;"> offset);</span></span>
<span class="line"><span style="color:#24292E;">  }</span></span>
<span class="line"><span style="color:#24292E;">}</span></span></code></pre></div></li></ol><h3 id="performlayout-流程" tabindex="-1">performLayout 流程 <a class="header-anchor" href="#performlayout-流程" aria-label="Permalink to &quot;performLayout 流程&quot;">​</a></h3><p>可以看到，布局的逻辑是在 performLayout 方法中实现的。我们梳理一下 performLayout 中具体做的事：</p><ol><li>如果有子组件，则对子组件进行递归布局。</li><li>确定当前组件的大小（size），通常会依赖子组件的大小。</li><li>确定子组件在当前组件中的起始偏移。</li></ol><p>在Flutter组件库中，有一些常用的单子组件比如Align、SizedBox、DecoratedBox等，都可以打开源码去看看其实现。</p><p>下面我们看一个多子组件的例子。</p><h2 id="_14-4-2-多子组件布局示例-leftrightbox" tabindex="-1">14.4.2 多子组件布局示例（LeftRightBox） <a class="header-anchor" href="#_14-4-2-多子组件布局示例-leftrightbox" aria-label="Permalink to &quot;14.4.2 多子组件布局示例（LeftRightBox）&quot;">​</a></h2><p>实际开发中我们会经常用到贴边左-右布局，现在我们就来实现一个 LeftRightBox 组件来实现左-右布局，因为LeftRightBox 有两个孩子，用一个 Widget 数组来保存子组件。</p><p>首先我们定义组件，与单子组件不同的是多子组件需要继承自 MultiChildRenderObjectWidget：</p><div class="language-dart vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre class="shiki github-dark vp-code-dark"><code><span class="line"><span style="color:#F97583;">class</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">LeftRightBox</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">extends</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">MultiChildRenderObjectWidget</span><span style="color:#E1E4E8;"> {</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">LeftRightBox</span><span style="color:#E1E4E8;">({</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#79B8FF;">Key</span><span style="color:#F97583;">?</span><span style="color:#E1E4E8;"> key,</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">required</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">List</span><span style="color:#E1E4E8;">&lt;</span><span style="color:#79B8FF;">Widget</span><span style="color:#E1E4E8;">&gt; children,</span></span>
<span class="line"><span style="color:#E1E4E8;">  })  </span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">assert</span><span style="color:#E1E4E8;">(children.length </span><span style="color:#F97583;">==</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">2</span><span style="color:#E1E4E8;">, </span><span style="color:#9ECBFF;">&quot;只能传两个children&quot;</span><span style="color:#E1E4E8;">),</span></span>
<span class="line"><span style="color:#E1E4E8;">        </span><span style="color:#79B8FF;">super</span><span style="color:#E1E4E8;">(key</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> key, children</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> children);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">@override</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">RenderObject</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">createRenderObject</span><span style="color:#E1E4E8;">(</span><span style="color:#79B8FF;">BuildContext</span><span style="color:#E1E4E8;"> context) {</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">return</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">RenderLeftRight</span><span style="color:#E1E4E8;">();</span></span>
<span class="line"><span style="color:#E1E4E8;">  }</span></span>
<span class="line"><span style="color:#E1E4E8;">}</span></span></code></pre><pre class="shiki github-light vp-code-light"><code><span class="line"><span style="color:#D73A49;">class</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">LeftRightBox</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">extends</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">MultiChildRenderObjectWidget</span><span style="color:#24292E;"> {</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">LeftRightBox</span><span style="color:#24292E;">({</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#005CC5;">Key</span><span style="color:#D73A49;">?</span><span style="color:#24292E;"> key,</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">required</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">List</span><span style="color:#24292E;">&lt;</span><span style="color:#005CC5;">Widget</span><span style="color:#24292E;">&gt; children,</span></span>
<span class="line"><span style="color:#24292E;">  })  </span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">assert</span><span style="color:#24292E;">(children.length </span><span style="color:#D73A49;">==</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">2</span><span style="color:#24292E;">, </span><span style="color:#032F62;">&quot;只能传两个children&quot;</span><span style="color:#24292E;">),</span></span>
<span class="line"><span style="color:#24292E;">        </span><span style="color:#005CC5;">super</span><span style="color:#24292E;">(key</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> key, children</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> children);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">@override</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">RenderObject</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">createRenderObject</span><span style="color:#24292E;">(</span><span style="color:#005CC5;">BuildContext</span><span style="color:#24292E;"> context) {</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">return</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">RenderLeftRight</span><span style="color:#24292E;">();</span></span>
<span class="line"><span style="color:#24292E;">  }</span></span>
<span class="line"><span style="color:#24292E;">}</span></span></code></pre></div><p>接下来需要实现 RenderLeftRight，在其 performLayout 中我们实现实现左-右布局算法：</p><div class="language-dart vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre class="shiki github-dark vp-code-dark"><code><span class="line"><span style="color:#F97583;">class</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">LeftRightParentData</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">extends</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">ContainerBoxParentData</span><span style="color:#E1E4E8;">&lt;</span><span style="color:#79B8FF;">RenderBox</span><span style="color:#E1E4E8;">&gt; {}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583;">class</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">RenderLeftRight</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">extends</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">RenderBox</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">with</span></span>
<span class="line"><span style="color:#E1E4E8;">        </span><span style="color:#79B8FF;">ContainerRenderObjectMixin</span><span style="color:#E1E4E8;">&lt;</span><span style="color:#79B8FF;">RenderBox</span><span style="color:#E1E4E8;">, </span><span style="color:#79B8FF;">LeftRightParentData</span><span style="color:#E1E4E8;">&gt;,</span></span>
<span class="line"><span style="color:#E1E4E8;">        </span><span style="color:#79B8FF;">RenderBoxContainerDefaultsMixin</span><span style="color:#E1E4E8;">&lt;</span><span style="color:#79B8FF;">RenderBox</span><span style="color:#E1E4E8;">, </span><span style="color:#79B8FF;">LeftRightParentData</span><span style="color:#E1E4E8;">&gt; {</span></span>
<span class="line"><span style="color:#E1E4E8;"> </span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#6A737D;">// 初始化每一个child的parentData        </span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">@override</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">void</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">setupParentData</span><span style="color:#E1E4E8;">(</span><span style="color:#79B8FF;">RenderBox</span><span style="color:#E1E4E8;"> child) {</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">if</span><span style="color:#E1E4E8;"> (child.parentData </span><span style="color:#F97583;">is!</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">LeftRightParentData</span><span style="color:#E1E4E8;">)</span></span>
<span class="line"><span style="color:#E1E4E8;">      child.parentData </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">LeftRightParentData</span><span style="color:#E1E4E8;">();</span></span>
<span class="line"><span style="color:#E1E4E8;">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">@override</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">void</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">performLayout</span><span style="color:#E1E4E8;">() {</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">final</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">BoxConstraints</span><span style="color:#E1E4E8;"> constraints </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">this</span><span style="color:#E1E4E8;">.constraints;</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#79B8FF;">RenderBox</span><span style="color:#E1E4E8;"> leftChild </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> firstChild</span><span style="color:#F97583;">!</span><span style="color:#E1E4E8;">;</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#79B8FF;">LeftRightParentData</span><span style="color:#E1E4E8;"> childParentData </span><span style="color:#F97583;">=</span></span>
<span class="line"><span style="color:#E1E4E8;">        leftChild.parentData</span><span style="color:#F97583;">!</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">as</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">LeftRightParentData</span><span style="color:#E1E4E8;">;</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#79B8FF;">RenderBox</span><span style="color:#E1E4E8;"> rightChild </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> childParentData.nextSibling</span><span style="color:#F97583;">!</span><span style="color:#E1E4E8;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">//我们限制右孩子宽度不超过总宽度一半</span></span>
<span class="line"><span style="color:#E1E4E8;">    rightChild.</span><span style="color:#B392F0;">layout</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">      constraints.</span><span style="color:#B392F0;">copyWith</span><span style="color:#E1E4E8;">(maxWidth</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> constraints.maxWidth </span><span style="color:#F97583;">/</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">2</span><span style="color:#E1E4E8;">),</span></span>
<span class="line"><span style="color:#E1E4E8;">      parentUsesSize</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">true</span><span style="color:#E1E4E8;">,</span></span>
<span class="line"><span style="color:#E1E4E8;">    );</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">//调整右子节点的offset</span></span>
<span class="line"><span style="color:#E1E4E8;">    childParentData </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> rightChild.parentData</span><span style="color:#F97583;">!</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">as</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">LeftRightParentData</span><span style="color:#E1E4E8;">;</span></span>
<span class="line"><span style="color:#E1E4E8;">    childParentData.offset </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Offset</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">      constraints.maxWidth </span><span style="color:#F97583;">-</span><span style="color:#E1E4E8;"> rightChild.size.width,</span></span>
<span class="line"><span style="color:#E1E4E8;">      </span><span style="color:#79B8FF;">0</span><span style="color:#E1E4E8;">,</span></span>
<span class="line"><span style="color:#E1E4E8;">    );</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">// layout left child</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">// 左子节点的offset默认为（0，0），为了确保左子节点始终能显示，我们不修改它的offset</span></span>
<span class="line"><span style="color:#E1E4E8;">    leftChild.</span><span style="color:#B392F0;">layout</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">      </span><span style="color:#6A737D;">//左侧剩余的最大宽度</span></span>
<span class="line"><span style="color:#E1E4E8;">      constraints.</span><span style="color:#B392F0;">copyWith</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">        maxWidth</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> constraints.maxWidth </span><span style="color:#F97583;">-</span><span style="color:#E1E4E8;"> rightChild.size.width,</span></span>
<span class="line"><span style="color:#E1E4E8;">      ),</span></span>
<span class="line"><span style="color:#E1E4E8;">      parentUsesSize</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">true</span><span style="color:#E1E4E8;">,</span></span>
<span class="line"><span style="color:#E1E4E8;">    );</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">//设置LeftRight自身的size</span></span>
<span class="line"><span style="color:#E1E4E8;">    size </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Size</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">      constraints.maxWidth,</span></span>
<span class="line"><span style="color:#E1E4E8;">      </span><span style="color:#B392F0;">max</span><span style="color:#E1E4E8;">(leftChild.size.height, rightChild.size.height),</span></span>
<span class="line"><span style="color:#E1E4E8;">    );</span></span>
<span class="line"><span style="color:#E1E4E8;">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">@override</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">void</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">paint</span><span style="color:#E1E4E8;">(</span><span style="color:#79B8FF;">PaintingContext</span><span style="color:#E1E4E8;"> context, </span><span style="color:#79B8FF;">Offset</span><span style="color:#E1E4E8;"> offset) {</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#B392F0;">defaultPaint</span><span style="color:#E1E4E8;">(context, offset);</span></span>
<span class="line"><span style="color:#E1E4E8;">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">@override</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">bool</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">hitTestChildren</span><span style="color:#E1E4E8;">(</span><span style="color:#79B8FF;">BoxHitTestResult</span><span style="color:#E1E4E8;"> result, {</span><span style="color:#F97583;">required</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Offset</span><span style="color:#E1E4E8;"> position}) {</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">return</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">defaultHitTestChildren</span><span style="color:#E1E4E8;">(result, position</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> position);</span></span>
<span class="line"><span style="color:#E1E4E8;">  }</span></span>
<span class="line"><span style="color:#E1E4E8;">}</span></span></code></pre><pre class="shiki github-light vp-code-light"><code><span class="line"><span style="color:#D73A49;">class</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">LeftRightParentData</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">extends</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">ContainerBoxParentData</span><span style="color:#24292E;">&lt;</span><span style="color:#005CC5;">RenderBox</span><span style="color:#24292E;">&gt; {}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;">class</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">RenderLeftRight</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">extends</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">RenderBox</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">with</span></span>
<span class="line"><span style="color:#24292E;">        </span><span style="color:#005CC5;">ContainerRenderObjectMixin</span><span style="color:#24292E;">&lt;</span><span style="color:#005CC5;">RenderBox</span><span style="color:#24292E;">, </span><span style="color:#005CC5;">LeftRightParentData</span><span style="color:#24292E;">&gt;,</span></span>
<span class="line"><span style="color:#24292E;">        </span><span style="color:#005CC5;">RenderBoxContainerDefaultsMixin</span><span style="color:#24292E;">&lt;</span><span style="color:#005CC5;">RenderBox</span><span style="color:#24292E;">, </span><span style="color:#005CC5;">LeftRightParentData</span><span style="color:#24292E;">&gt; {</span></span>
<span class="line"><span style="color:#24292E;"> </span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#6A737D;">// 初始化每一个child的parentData        </span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">@override</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">void</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">setupParentData</span><span style="color:#24292E;">(</span><span style="color:#005CC5;">RenderBox</span><span style="color:#24292E;"> child) {</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">if</span><span style="color:#24292E;"> (child.parentData </span><span style="color:#D73A49;">is!</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">LeftRightParentData</span><span style="color:#24292E;">)</span></span>
<span class="line"><span style="color:#24292E;">      child.parentData </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">LeftRightParentData</span><span style="color:#24292E;">();</span></span>
<span class="line"><span style="color:#24292E;">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">@override</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">void</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">performLayout</span><span style="color:#24292E;">() {</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">final</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">BoxConstraints</span><span style="color:#24292E;"> constraints </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">this</span><span style="color:#24292E;">.constraints;</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#005CC5;">RenderBox</span><span style="color:#24292E;"> leftChild </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> firstChild</span><span style="color:#D73A49;">!</span><span style="color:#24292E;">;</span></span>
<span class="line"><span style="color:#24292E;">    </span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#005CC5;">LeftRightParentData</span><span style="color:#24292E;"> childParentData </span><span style="color:#D73A49;">=</span></span>
<span class="line"><span style="color:#24292E;">        leftChild.parentData</span><span style="color:#D73A49;">!</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">as</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">LeftRightParentData</span><span style="color:#24292E;">;</span></span>
<span class="line"><span style="color:#24292E;">    </span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#005CC5;">RenderBox</span><span style="color:#24292E;"> rightChild </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> childParentData.nextSibling</span><span style="color:#D73A49;">!</span><span style="color:#24292E;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">//我们限制右孩子宽度不超过总宽度一半</span></span>
<span class="line"><span style="color:#24292E;">    rightChild.</span><span style="color:#6F42C1;">layout</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">      constraints.</span><span style="color:#6F42C1;">copyWith</span><span style="color:#24292E;">(maxWidth</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> constraints.maxWidth </span><span style="color:#D73A49;">/</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">2</span><span style="color:#24292E;">),</span></span>
<span class="line"><span style="color:#24292E;">      parentUsesSize</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">true</span><span style="color:#24292E;">,</span></span>
<span class="line"><span style="color:#24292E;">    );</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">//调整右子节点的offset</span></span>
<span class="line"><span style="color:#24292E;">    childParentData </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> rightChild.parentData</span><span style="color:#D73A49;">!</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">as</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">LeftRightParentData</span><span style="color:#24292E;">;</span></span>
<span class="line"><span style="color:#24292E;">    childParentData.offset </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Offset</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">      constraints.maxWidth </span><span style="color:#D73A49;">-</span><span style="color:#24292E;"> rightChild.size.width,</span></span>
<span class="line"><span style="color:#24292E;">      </span><span style="color:#005CC5;">0</span><span style="color:#24292E;">,</span></span>
<span class="line"><span style="color:#24292E;">    );</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">// layout left child</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">// 左子节点的offset默认为（0，0），为了确保左子节点始终能显示，我们不修改它的offset</span></span>
<span class="line"><span style="color:#24292E;">    leftChild.</span><span style="color:#6F42C1;">layout</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">      </span><span style="color:#6A737D;">//左侧剩余的最大宽度</span></span>
<span class="line"><span style="color:#24292E;">      constraints.</span><span style="color:#6F42C1;">copyWith</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">        maxWidth</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> constraints.maxWidth </span><span style="color:#D73A49;">-</span><span style="color:#24292E;"> rightChild.size.width,</span></span>
<span class="line"><span style="color:#24292E;">      ),</span></span>
<span class="line"><span style="color:#24292E;">      parentUsesSize</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">true</span><span style="color:#24292E;">,</span></span>
<span class="line"><span style="color:#24292E;">    );</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">//设置LeftRight自身的size</span></span>
<span class="line"><span style="color:#24292E;">    size </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Size</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">      constraints.maxWidth,</span></span>
<span class="line"><span style="color:#24292E;">      </span><span style="color:#6F42C1;">max</span><span style="color:#24292E;">(leftChild.size.height, rightChild.size.height),</span></span>
<span class="line"><span style="color:#24292E;">    );</span></span>
<span class="line"><span style="color:#24292E;">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">@override</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">void</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">paint</span><span style="color:#24292E;">(</span><span style="color:#005CC5;">PaintingContext</span><span style="color:#24292E;"> context, </span><span style="color:#005CC5;">Offset</span><span style="color:#24292E;"> offset) {</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6F42C1;">defaultPaint</span><span style="color:#24292E;">(context, offset);</span></span>
<span class="line"><span style="color:#24292E;">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">@override</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">bool</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">hitTestChildren</span><span style="color:#24292E;">(</span><span style="color:#005CC5;">BoxHitTestResult</span><span style="color:#24292E;"> result, {</span><span style="color:#D73A49;">required</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Offset</span><span style="color:#24292E;"> position}) {</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">return</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">defaultHitTestChildren</span><span style="color:#24292E;">(result, position</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> position);</span></span>
<span class="line"><span style="color:#24292E;">  }</span></span>
<span class="line"><span style="color:#24292E;">}</span></span></code></pre></div><p>可以看到，实际布局流程和单子节点并没有太大区别，只不过多子组件需要同时对多个子节点进行布局。另外和RenderCustomCenter 不同的是，RenderLeftRight是直接继承自 RenderBox，同时混入了ContainerRenderObjectMixin 和 RenderBoxContainerDefaultsMixin 两个 mixin，这两个 mixin 实现了通用的绘制和事件处理相关逻辑（现在先不用关注，后面章节会讲）。</p><h2 id="_14-4-3-关于parentdata" tabindex="-1">14.4.3 关于ParentData <a class="header-anchor" href="#_14-4-3-关于parentdata" aria-label="Permalink to &quot;14.4.3 关于ParentData&quot;">​</a></h2><p>上面两个例子中我们在实现相应的 RenderObject 时都用到了子节点的 parentData 对象(将子节点的offset信息保存其中)，可以看到 parentData 虽然属于child的属性，但它从设置（包括初始化）到使用都在父节点中，这也是为什么起名叫“parentData”。实际上Flutter框架中，parentData 这个属性主要就是为了在 layout 阶段保存组件布局信息而设计的。</p><p>需要注意：“parentData 用于保存节点的布局信息” 只是一个约定，我们定义组件时完全可以将子节点的布局信息保存在任意地方，也可以保存非布局信息。但是，还是强烈建议大家遵循Flutter的规范，这样我们的代码会更容易被他人看懂，也会更容易维护。</p><h2 id="_14-4-4-布局更新" tabindex="-1">14.4.4 布局更新 <a class="header-anchor" href="#_14-4-4-布局更新" aria-label="Permalink to &quot;14.4.4 布局更新&quot;">​</a></h2><p>理论上，某个组件的布局变化后，就可能会影响其他组件的布局，所以当有组件布局发生变化后，最笨的办法是对整棵组件树 relayout（重新布局）！但是对所有组件进行 relayout 的成本还是太大，所以我们需要探索一下降低 relayout 成本的方案。实际上，在一些特定场景下，组件发生变化后我们只需要对部分组件进行重新布局即可（而无需对整棵树 relayout ）。</p><h3 id="_1-布局边界-relayoutboundary" tabindex="-1">1. 布局边界（relayoutBoundary） <a class="header-anchor" href="#_1-布局边界-relayoutboundary" aria-label="Permalink to &quot;1. 布局边界（relayoutBoundary）&quot;">​</a></h3><p>假如有一个页面的组件树结构如图14-5所示：</p><p><img src="/assets/14-5.3d281a7d.png" alt="图14-5"></p><p>假如 Text3 的文本长度发生变化，则会导致 Text4 的位置和 Column2 的大小也会变化；又因为 Column2 的父组件 SizedBox 已经限定了大小，所以 SizedBox 的大小和位置都不会变化。所以最终我们需要进行 relayout 的组件是：Text3、Column2，这里需要注意：</p><ol><li>Text4 是不需要重新布局的，因为 Text4 的大小没有发生变化，只是位置发生变化，而它的位置是在父组件 Column2 布局时确定的。</li><li>很容易发现：假如 Text3 和 Column2 之间还有其他组件，则这些组件也都是需要 relayout 的。</li></ol><p>在本例中，Column2 就是 Text3 的 relayoutBoundary （重新布局的边界节点）。每个组件的 renderObject 中都有一个 <code>_relayoutBoundary</code> 属性指向自身的布局边界节点，如果当前节点布局发生变化后，自身到其布局边界节点路径上的所有的节点都需要 relayout。</p><p>那么，一个组件是否是 relayoutBoundary 的条件是什么呢？这里有一个原则和四个场景，原则是“组件自身的大小变化不会影响父组件”，如果一个组件满足以下四种情况之一，则它便是 relayoutBoundary ：</p><ol><li><p>当前组件父组件的大小不依赖当前组件大小时；这种情况下父组件在布局时会调用子组件布局函数时并会给子组件传递一个 parentUsesSize 参数，该参数为 false 时表示父组件的布局算法不会依赖子组件的大小。</p></li><li><p>组件的大小只取决于父组件传递的约束，而不会依赖后代组件的大小。这样的话后代组件的大小变化就不会影响自身的大小了，这种情况组件的 sizedByParent 属性必须为 true（具体我们后面会讲）。</p></li><li><p>父组件传递给自身的约束是一个严格约束（固定宽高，下面会讲）；这种情况下即使自身的大小依赖后代元素，但也不会影响父组件。</p></li><li><p>组件为根组件；Flutter 应用的根组件是 RenderView，它的默认大小是当前设备屏幕大小。</p></li></ol><p>对应的代码实现是：</p><div class="language-dart vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre class="shiki github-dark vp-code-dark"><code><span class="line"><span style="color:#6A737D;">// parent is! RenderObject 为 true 时则表示当前组件是根组件，因为只有根组件没有父组件。</span></span>
<span class="line"><span style="color:#F97583;">if</span><span style="color:#E1E4E8;"> (</span><span style="color:#F97583;">!</span><span style="color:#E1E4E8;">parentUsesSize </span><span style="color:#F97583;">||</span><span style="color:#E1E4E8;"> sizedByParent </span><span style="color:#F97583;">||</span><span style="color:#E1E4E8;"> constraints.isTight </span><span style="color:#F97583;">||</span><span style="color:#E1E4E8;"> parent </span><span style="color:#F97583;">is!</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">RenderObject</span><span style="color:#E1E4E8;">) {</span></span>
<span class="line"><span style="color:#E1E4E8;">  _relayoutBoundary </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">this</span><span style="color:#E1E4E8;">;</span></span>
<span class="line"><span style="color:#E1E4E8;">} </span><span style="color:#F97583;">else</span><span style="color:#E1E4E8;"> {</span></span>
<span class="line"><span style="color:#E1E4E8;">  _relayoutBoundary </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> (parent</span><span style="color:#F97583;">!</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">as</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">RenderObject</span><span style="color:#E1E4E8;">)._relayoutBoundary;</span></span>
<span class="line"><span style="color:#E1E4E8;">}</span></span></code></pre><pre class="shiki github-light vp-code-light"><code><span class="line"><span style="color:#6A737D;">// parent is! RenderObject 为 true 时则表示当前组件是根组件，因为只有根组件没有父组件。</span></span>
<span class="line"><span style="color:#D73A49;">if</span><span style="color:#24292E;"> (</span><span style="color:#D73A49;">!</span><span style="color:#24292E;">parentUsesSize </span><span style="color:#D73A49;">||</span><span style="color:#24292E;"> sizedByParent </span><span style="color:#D73A49;">||</span><span style="color:#24292E;"> constraints.isTight </span><span style="color:#D73A49;">||</span><span style="color:#24292E;"> parent </span><span style="color:#D73A49;">is!</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">RenderObject</span><span style="color:#24292E;">) {</span></span>
<span class="line"><span style="color:#24292E;">  _relayoutBoundary </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">this</span><span style="color:#24292E;">;</span></span>
<span class="line"><span style="color:#24292E;">} </span><span style="color:#D73A49;">else</span><span style="color:#24292E;"> {</span></span>
<span class="line"><span style="color:#24292E;">  _relayoutBoundary </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> (parent</span><span style="color:#D73A49;">!</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">as</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">RenderObject</span><span style="color:#24292E;">)._relayoutBoundary;</span></span>
<span class="line"><span style="color:#24292E;">}</span></span></code></pre></div><p>代码中 if 里的判断条件和上面的 4 条 一一对应，其中除了第二个条件之外（ sizedByParent 为 true ），其他的都很直观，我们会在后面专门介绍一下第二个条件。</p><h3 id="_2-markneedslayout" tabindex="-1">2. markNeedsLayout <a class="header-anchor" href="#_2-markneedslayout" aria-label="Permalink to &quot;2. markNeedsLayout&quot;">​</a></h3><p>当组件布局发生变化时，它需要调用 <code>markNeedsLayout</code> 方法来更新布局，它的功能主要有两个：</p><ol><li>将自身到其 relayoutBoundary 路径上的所有节点标记为 “需要布局” 。</li><li>请求新的 frame；在新的 frame 中会对标记为“需要布局”的节点重新布局。</li></ol><p>我们看看其核心源码：</p><div class="language-dart vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre class="shiki github-dark vp-code-dark"><code><span class="line"><span style="color:#F97583;">void</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">markNeedsLayout</span><span style="color:#E1E4E8;">() {</span></span>
<span class="line"><span style="color:#E1E4E8;">   _needsLayout </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">true</span><span style="color:#E1E4E8;">;</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">if</span><span style="color:#E1E4E8;"> (_relayoutBoundary </span><span style="color:#F97583;">!=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">this</span><span style="color:#E1E4E8;">) { </span><span style="color:#6A737D;">// 如果不是布局边界节点</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#B392F0;">markParentNeedsLayout</span><span style="color:#E1E4E8;">(); </span><span style="color:#6A737D;">// 递归调用前节点到其布局边界节点路径上所有节点的方法 markNeedsLayout</span></span>
<span class="line"><span style="color:#E1E4E8;">  } </span><span style="color:#F97583;">else</span><span style="color:#E1E4E8;"> {</span><span style="color:#6A737D;">// 如果是布局边界节点 </span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">if</span><span style="color:#E1E4E8;"> (owner </span><span style="color:#F97583;">!=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">null</span><span style="color:#E1E4E8;">) {</span></span>
<span class="line"><span style="color:#E1E4E8;">      </span><span style="color:#6A737D;">// 将布局边界节点加入到 pipelineOwner._nodesNeedingLayout 列表中</span></span>
<span class="line"><span style="color:#E1E4E8;">      owner</span><span style="color:#F97583;">!</span><span style="color:#E1E4E8;">._nodesNeedingLayout.</span><span style="color:#B392F0;">add</span><span style="color:#E1E4E8;">(</span><span style="color:#79B8FF;">this</span><span style="color:#E1E4E8;">); </span></span>
<span class="line"><span style="color:#E1E4E8;">      owner</span><span style="color:#F97583;">!</span><span style="color:#E1E4E8;">.</span><span style="color:#B392F0;">requestVisualUpdate</span><span style="color:#E1E4E8;">();</span><span style="color:#6A737D;">//该函数最终会请求新的 frame</span></span>
<span class="line"><span style="color:#E1E4E8;">    }</span></span>
<span class="line"><span style="color:#E1E4E8;">  }</span></span>
<span class="line"><span style="color:#E1E4E8;">}</span></span></code></pre><pre class="shiki github-light vp-code-light"><code><span class="line"><span style="color:#D73A49;">void</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">markNeedsLayout</span><span style="color:#24292E;">() {</span></span>
<span class="line"><span style="color:#24292E;">   _needsLayout </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">true</span><span style="color:#24292E;">;</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">if</span><span style="color:#24292E;"> (_relayoutBoundary </span><span style="color:#D73A49;">!=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">this</span><span style="color:#24292E;">) { </span><span style="color:#6A737D;">// 如果不是布局边界节点</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6F42C1;">markParentNeedsLayout</span><span style="color:#24292E;">(); </span><span style="color:#6A737D;">// 递归调用前节点到其布局边界节点路径上所有节点的方法 markNeedsLayout</span></span>
<span class="line"><span style="color:#24292E;">  } </span><span style="color:#D73A49;">else</span><span style="color:#24292E;"> {</span><span style="color:#6A737D;">// 如果是布局边界节点 </span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">if</span><span style="color:#24292E;"> (owner </span><span style="color:#D73A49;">!=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">null</span><span style="color:#24292E;">) {</span></span>
<span class="line"><span style="color:#24292E;">      </span><span style="color:#6A737D;">// 将布局边界节点加入到 pipelineOwner._nodesNeedingLayout 列表中</span></span>
<span class="line"><span style="color:#24292E;">      owner</span><span style="color:#D73A49;">!</span><span style="color:#24292E;">._nodesNeedingLayout.</span><span style="color:#6F42C1;">add</span><span style="color:#24292E;">(</span><span style="color:#005CC5;">this</span><span style="color:#24292E;">); </span></span>
<span class="line"><span style="color:#24292E;">      owner</span><span style="color:#D73A49;">!</span><span style="color:#24292E;">.</span><span style="color:#6F42C1;">requestVisualUpdate</span><span style="color:#24292E;">();</span><span style="color:#6A737D;">//该函数最终会请求新的 frame</span></span>
<span class="line"><span style="color:#24292E;">    }</span></span>
<span class="line"><span style="color:#24292E;">  }</span></span>
<span class="line"><span style="color:#24292E;">}</span></span></code></pre></div><h3 id="_3-flushlayout" tabindex="-1">3. flushLayout() <a class="header-anchor" href="#_3-flushlayout" aria-label="Permalink to &quot;3. flushLayout()&quot;">​</a></h3><p>markNeedsLayout 执行完毕后，就会将其 relayoutBoundary 节点添加到 <code>pipelineOwner._nodesNeedingLayout</code> 列表中，然后请求新的 frame，新的 frame 到来时就会执行 <code>drawFrame</code> 方法（可以参考上一节）：</p><div class="language-dart vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre class="shiki github-dark vp-code-dark"><code><span class="line"><span style="color:#F97583;">void</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">drawFrame</span><span style="color:#E1E4E8;">() {</span></span>
<span class="line"><span style="color:#E1E4E8;">  pipelineOwner.</span><span style="color:#B392F0;">flushLayout</span><span style="color:#E1E4E8;">(); </span><span style="color:#6A737D;">//重新布局</span></span>
<span class="line"><span style="color:#E1E4E8;">  pipelineOwner.</span><span style="color:#B392F0;">flushCompositingBits</span><span style="color:#E1E4E8;">();</span></span>
<span class="line"><span style="color:#E1E4E8;">  pipelineOwner.</span><span style="color:#B392F0;">flushPaint</span><span style="color:#E1E4E8;">();</span></span>
<span class="line"><span style="color:#E1E4E8;">  ...</span></span>
<span class="line"><span style="color:#E1E4E8;">}</span></span></code></pre><pre class="shiki github-light vp-code-light"><code><span class="line"><span style="color:#D73A49;">void</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">drawFrame</span><span style="color:#24292E;">() {</span></span>
<span class="line"><span style="color:#24292E;">  pipelineOwner.</span><span style="color:#6F42C1;">flushLayout</span><span style="color:#24292E;">(); </span><span style="color:#6A737D;">//重新布局</span></span>
<span class="line"><span style="color:#24292E;">  pipelineOwner.</span><span style="color:#6F42C1;">flushCompositingBits</span><span style="color:#24292E;">();</span></span>
<span class="line"><span style="color:#24292E;">  pipelineOwner.</span><span style="color:#6F42C1;">flushPaint</span><span style="color:#24292E;">();</span></span>
<span class="line"><span style="color:#24292E;">  ...</span></span>
<span class="line"><span style="color:#24292E;">}</span></span></code></pre></div><p>flushLayout() 中会对之前添加到 <code>_nodesNeedingLayout</code> 中的节点重新布局，我们看一下其核心源码：</p><div class="language-dart vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre class="shiki github-dark vp-code-dark"><code><span class="line"><span style="color:#F97583;">void</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">flushLayout</span><span style="color:#E1E4E8;">() {</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">while</span><span style="color:#E1E4E8;"> (_nodesNeedingLayout.isNotEmpty) {</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">final</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">List</span><span style="color:#E1E4E8;">&lt;</span><span style="color:#79B8FF;">RenderObject</span><span style="color:#E1E4E8;">&gt; dirtyNodes </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> _nodesNeedingLayout;</span></span>
<span class="line"><span style="color:#E1E4E8;">    _nodesNeedingLayout </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">&lt;</span><span style="color:#79B8FF;">RenderObject</span><span style="color:#F97583;">&gt;</span><span style="color:#E1E4E8;">[]; </span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">//按照节点在树中的深度从小到大排序后再重新layout</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">for</span><span style="color:#E1E4E8;"> (</span><span style="color:#F97583;">final</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">RenderObject</span><span style="color:#E1E4E8;"> node </span><span style="color:#F97583;">in</span><span style="color:#E1E4E8;"> dirtyNodes..</span><span style="color:#B392F0;">sort</span><span style="color:#E1E4E8;">((a,b) </span><span style="color:#F97583;">=&gt;</span><span style="color:#E1E4E8;"> a.depth </span><span style="color:#F97583;">-</span><span style="color:#E1E4E8;"> b.depth)) {</span></span>
<span class="line"><span style="color:#E1E4E8;">      </span><span style="color:#F97583;">if</span><span style="color:#E1E4E8;"> (node._needsLayout </span><span style="color:#F97583;">&amp;&amp;</span><span style="color:#E1E4E8;"> node.owner </span><span style="color:#F97583;">==</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">this</span><span style="color:#E1E4E8;">)</span></span>
<span class="line"><span style="color:#E1E4E8;">        node.</span><span style="color:#B392F0;">_layoutWithoutResize</span><span style="color:#E1E4E8;">(); </span><span style="color:#6A737D;">//重新布局</span></span>
<span class="line"><span style="color:#E1E4E8;">    }</span></span>
<span class="line"><span style="color:#E1E4E8;">  }</span></span>
<span class="line"><span style="color:#E1E4E8;">}</span></span></code></pre><pre class="shiki github-light vp-code-light"><code><span class="line"><span style="color:#D73A49;">void</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">flushLayout</span><span style="color:#24292E;">() {</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">while</span><span style="color:#24292E;"> (_nodesNeedingLayout.isNotEmpty) {</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">final</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">List</span><span style="color:#24292E;">&lt;</span><span style="color:#005CC5;">RenderObject</span><span style="color:#24292E;">&gt; dirtyNodes </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> _nodesNeedingLayout;</span></span>
<span class="line"><span style="color:#24292E;">    _nodesNeedingLayout </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">&lt;</span><span style="color:#005CC5;">RenderObject</span><span style="color:#D73A49;">&gt;</span><span style="color:#24292E;">[]; </span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">//按照节点在树中的深度从小到大排序后再重新layout</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">for</span><span style="color:#24292E;"> (</span><span style="color:#D73A49;">final</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">RenderObject</span><span style="color:#24292E;"> node </span><span style="color:#D73A49;">in</span><span style="color:#24292E;"> dirtyNodes..</span><span style="color:#6F42C1;">sort</span><span style="color:#24292E;">((a,b) </span><span style="color:#D73A49;">=&gt;</span><span style="color:#24292E;"> a.depth </span><span style="color:#D73A49;">-</span><span style="color:#24292E;"> b.depth)) {</span></span>
<span class="line"><span style="color:#24292E;">      </span><span style="color:#D73A49;">if</span><span style="color:#24292E;"> (node._needsLayout </span><span style="color:#D73A49;">&amp;&amp;</span><span style="color:#24292E;"> node.owner </span><span style="color:#D73A49;">==</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">this</span><span style="color:#24292E;">)</span></span>
<span class="line"><span style="color:#24292E;">        node.</span><span style="color:#6F42C1;">_layoutWithoutResize</span><span style="color:#24292E;">(); </span><span style="color:#6A737D;">//重新布局</span></span>
<span class="line"><span style="color:#24292E;">    }</span></span>
<span class="line"><span style="color:#24292E;">  }</span></span>
<span class="line"><span style="color:#24292E;">}</span></span></code></pre></div><p>看一下 <code>_layoutWithoutResize</code> 实现</p><div class="language-dart vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre class="shiki github-dark vp-code-dark"><code><span class="line"><span style="color:#F97583;">void</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">_layoutWithoutResize</span><span style="color:#E1E4E8;">() {</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#B392F0;">performLayout</span><span style="color:#E1E4E8;">(); </span><span style="color:#6A737D;">// 重新布局；会递归布局后代节点</span></span>
<span class="line"><span style="color:#E1E4E8;">  _needsLayout </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">false</span><span style="color:#E1E4E8;">;</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#B392F0;">markNeedsPaint</span><span style="color:#E1E4E8;">(); </span><span style="color:#6A737D;">//布局更新后，UI也是需要更新的</span></span>
<span class="line"><span style="color:#E1E4E8;">}</span></span></code></pre><pre class="shiki github-light vp-code-light"><code><span class="line"><span style="color:#D73A49;">void</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">_layoutWithoutResize</span><span style="color:#24292E;">() {</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#6F42C1;">performLayout</span><span style="color:#24292E;">(); </span><span style="color:#6A737D;">// 重新布局；会递归布局后代节点</span></span>
<span class="line"><span style="color:#24292E;">  _needsLayout </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">false</span><span style="color:#24292E;">;</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#6F42C1;">markNeedsPaint</span><span style="color:#24292E;">(); </span><span style="color:#6A737D;">//布局更新后，UI也是需要更新的</span></span>
<span class="line"><span style="color:#24292E;">}</span></span></code></pre></div><p>代码很简单，不再赘述。</p><blockquote><p>思考题：为什么 flushLayout() 中刷新布局时要先对dirtyNodes 根据在树中的深度按照从小到大排序？从大到小不行吗？</p></blockquote><h3 id="_4-layout流程" tabindex="-1">4. Layout流程 <a class="header-anchor" href="#_4-layout流程" aria-label="Permalink to &quot;4. Layout流程&quot;">​</a></h3><p>如果组件有子组件，则在 performLayout 中需要调用子组件的 layout 方法先对子组件进行布局，我们看一下 layout 的核心流程：</p><div class="language-dart vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre class="shiki github-dark vp-code-dark"><code><span class="line"><span style="color:#F97583;">void</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">layout</span><span style="color:#E1E4E8;">(</span><span style="color:#79B8FF;">Constraints</span><span style="color:#E1E4E8;"> constraints, { </span><span style="color:#79B8FF;">bool</span><span style="color:#E1E4E8;"> parentUsesSize </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">false</span><span style="color:#E1E4E8;"> }) {</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">RenderObject</span><span style="color:#F97583;">?</span><span style="color:#E1E4E8;"> relayoutBoundary;</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#6A737D;">// 先确定当前组件的布局边界</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">if</span><span style="color:#E1E4E8;"> (</span><span style="color:#F97583;">!</span><span style="color:#E1E4E8;">parentUsesSize </span><span style="color:#F97583;">||</span><span style="color:#E1E4E8;"> sizedByParent </span><span style="color:#F97583;">||</span><span style="color:#E1E4E8;"> constraints.isTight </span><span style="color:#F97583;">||</span><span style="color:#E1E4E8;"> parent </span><span style="color:#F97583;">is!</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">RenderObject</span><span style="color:#E1E4E8;">) {</span></span>
<span class="line"><span style="color:#E1E4E8;">    relayoutBoundary </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">this</span><span style="color:#E1E4E8;">;</span></span>
<span class="line"><span style="color:#E1E4E8;">  } </span><span style="color:#F97583;">else</span><span style="color:#E1E4E8;"> {</span></span>
<span class="line"><span style="color:#E1E4E8;">    relayoutBoundary </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> (parent</span><span style="color:#F97583;">!</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">as</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">RenderObject</span><span style="color:#E1E4E8;">)._relayoutBoundary;</span></span>
<span class="line"><span style="color:#E1E4E8;">  }</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#6A737D;">// _needsLayout 表示当前组件是否被标记为需要布局</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#6A737D;">// _constraints 是上次布局时父组件传递给当前组件的约束</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#6A737D;">// _relayoutBoundary 为上次布局时当前组件的布局边界</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#6A737D;">// 所以，当当前组件没有被标记为需要重新布局，且父组件传递的约束没有发生变化，</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#6A737D;">// 且布局边界也没有发生变化时则不需要重新布局，直接返回即可。</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">if</span><span style="color:#E1E4E8;"> (</span><span style="color:#F97583;">!</span><span style="color:#E1E4E8;">_needsLayout </span><span style="color:#F97583;">&amp;&amp;</span><span style="color:#E1E4E8;"> constraints </span><span style="color:#F97583;">==</span><span style="color:#E1E4E8;"> _constraints </span><span style="color:#F97583;">&amp;&amp;</span><span style="color:#E1E4E8;"> relayoutBoundary </span><span style="color:#F97583;">==</span><span style="color:#E1E4E8;"> _relayoutBoundary) {</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">return</span><span style="color:#E1E4E8;">;</span></span>
<span class="line"><span style="color:#E1E4E8;">  }</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#6A737D;">// 如果需要布局，缓存约束和布局边界</span></span>
<span class="line"><span style="color:#E1E4E8;">  _constraints </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> constraints;</span></span>
<span class="line"><span style="color:#E1E4E8;">  _relayoutBoundary </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> relayoutBoundary;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#6A737D;">// 后面解释</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">if</span><span style="color:#E1E4E8;"> (sizedByParent) {</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#B392F0;">performResize</span><span style="color:#E1E4E8;">();</span></span>
<span class="line"><span style="color:#E1E4E8;">  }</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#6A737D;">// 执行布局</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#B392F0;">performLayout</span><span style="color:#E1E4E8;">();</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#6A737D;">// 布局结束后将 _needsLayout 置为 false</span></span>
<span class="line"><span style="color:#E1E4E8;">  _needsLayout </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">false</span><span style="color:#E1E4E8;">;</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#6A737D;">// 将当前组件标记为需要重绘（因为布局发生变化后，需要重新绘制）</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#B392F0;">markNeedsPaint</span><span style="color:#E1E4E8;">();</span></span>
<span class="line"><span style="color:#E1E4E8;">}</span></span></code></pre><pre class="shiki github-light vp-code-light"><code><span class="line"><span style="color:#D73A49;">void</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">layout</span><span style="color:#24292E;">(</span><span style="color:#005CC5;">Constraints</span><span style="color:#24292E;"> constraints, { </span><span style="color:#005CC5;">bool</span><span style="color:#24292E;"> parentUsesSize </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">false</span><span style="color:#24292E;"> }) {</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">RenderObject</span><span style="color:#D73A49;">?</span><span style="color:#24292E;"> relayoutBoundary;</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#6A737D;">// 先确定当前组件的布局边界</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">if</span><span style="color:#24292E;"> (</span><span style="color:#D73A49;">!</span><span style="color:#24292E;">parentUsesSize </span><span style="color:#D73A49;">||</span><span style="color:#24292E;"> sizedByParent </span><span style="color:#D73A49;">||</span><span style="color:#24292E;"> constraints.isTight </span><span style="color:#D73A49;">||</span><span style="color:#24292E;"> parent </span><span style="color:#D73A49;">is!</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">RenderObject</span><span style="color:#24292E;">) {</span></span>
<span class="line"><span style="color:#24292E;">    relayoutBoundary </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">this</span><span style="color:#24292E;">;</span></span>
<span class="line"><span style="color:#24292E;">  } </span><span style="color:#D73A49;">else</span><span style="color:#24292E;"> {</span></span>
<span class="line"><span style="color:#24292E;">    relayoutBoundary </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> (parent</span><span style="color:#D73A49;">!</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">as</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">RenderObject</span><span style="color:#24292E;">)._relayoutBoundary;</span></span>
<span class="line"><span style="color:#24292E;">  }</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#6A737D;">// _needsLayout 表示当前组件是否被标记为需要布局</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#6A737D;">// _constraints 是上次布局时父组件传递给当前组件的约束</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#6A737D;">// _relayoutBoundary 为上次布局时当前组件的布局边界</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#6A737D;">// 所以，当当前组件没有被标记为需要重新布局，且父组件传递的约束没有发生变化，</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#6A737D;">// 且布局边界也没有发生变化时则不需要重新布局，直接返回即可。</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">if</span><span style="color:#24292E;"> (</span><span style="color:#D73A49;">!</span><span style="color:#24292E;">_needsLayout </span><span style="color:#D73A49;">&amp;&amp;</span><span style="color:#24292E;"> constraints </span><span style="color:#D73A49;">==</span><span style="color:#24292E;"> _constraints </span><span style="color:#D73A49;">&amp;&amp;</span><span style="color:#24292E;"> relayoutBoundary </span><span style="color:#D73A49;">==</span><span style="color:#24292E;"> _relayoutBoundary) {</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">return</span><span style="color:#24292E;">;</span></span>
<span class="line"><span style="color:#24292E;">  }</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#6A737D;">// 如果需要布局，缓存约束和布局边界</span></span>
<span class="line"><span style="color:#24292E;">  _constraints </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> constraints;</span></span>
<span class="line"><span style="color:#24292E;">  _relayoutBoundary </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> relayoutBoundary;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#6A737D;">// 后面解释</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">if</span><span style="color:#24292E;"> (sizedByParent) {</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6F42C1;">performResize</span><span style="color:#24292E;">();</span></span>
<span class="line"><span style="color:#24292E;">  }</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#6A737D;">// 执行布局</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#6F42C1;">performLayout</span><span style="color:#24292E;">();</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#6A737D;">// 布局结束后将 _needsLayout 置为 false</span></span>
<span class="line"><span style="color:#24292E;">  _needsLayout </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">false</span><span style="color:#24292E;">;</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#6A737D;">// 将当前组件标记为需要重绘（因为布局发生变化后，需要重新绘制）</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#6F42C1;">markNeedsPaint</span><span style="color:#24292E;">();</span></span>
<span class="line"><span style="color:#24292E;">}</span></span></code></pre></div><p>简单来讲布局过程分以下几步：</p><ol><li><p>确定当前组件的布局边界。</p></li><li><p>判断是否需要重新布局，如果没必要会直接返回，反之才需要重新布局。不需要布局时需要同时满足三个条件：</p><ul><li><p>当前组件没有被标记为需要重新布局。</p></li><li><p>父组件传递的约束没有发生变化。</p></li><li><p>当前组件的布局边界也没有发生变化时。</p></li></ul></li><li><p>调用 performLayout() 进行布局，因为 performLayout() 中又会调用子组件的 layout 方法，所以这时一个递归的过程，递归结束后整个组件树的布局也就完成了。</p></li><li><p>请求重绘。</p></li></ol><h2 id="_14-4-5-sizedbyparent" tabindex="-1">14.4.5 sizedByParent <a class="header-anchor" href="#_14-4-5-sizedbyparent" aria-label="Permalink to &quot;14.4.5 sizedByParent&quot;">​</a></h2><p>在 layout 方法中，有如下逻辑：</p><div class="language-dart vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre class="shiki github-dark vp-code-dark"><code><span class="line"><span style="color:#F97583;">if</span><span style="color:#E1E4E8;"> (sizedByParent) {</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#B392F0;">performResize</span><span style="color:#E1E4E8;">(); </span><span style="color:#6A737D;">//重新确定组件大小</span></span>
<span class="line"><span style="color:#E1E4E8;">}</span></span></code></pre><pre class="shiki github-light vp-code-light"><code><span class="line"><span style="color:#D73A49;">if</span><span style="color:#24292E;"> (sizedByParent) {</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#6F42C1;">performResize</span><span style="color:#24292E;">(); </span><span style="color:#6A737D;">//重新确定组件大小</span></span>
<span class="line"><span style="color:#24292E;">}</span></span></code></pre></div><p>上面我们说过 sizedByParent 为 true 时表示：当前组件的大小只取决于父组件传递的约束，而不会依赖后代组件的大小。前面我们说过，performLayout 中确定当前组件的大小时通常会依赖子组件的大小，如果 sizedByParent 为 true，则当前组件的大小就不依赖子组件大小了，为了逻辑清晰，Flutter 框架中约定，当sizedByParent 为 true 时，确定当前组件大小的逻辑应抽离到 performResize() 中，这种情况下 performLayout 主要的任务便只有两个：对子组件进行布局和确定子组件在当前组件中的布局起始位置偏移。</p><p>下面我们通过一个 AccurateSizedBox 示例来演示一下 sizedByParent 为 true 时我们应该如何布局：</p><h3 id="accuratesizedbox" tabindex="-1">AccurateSizedBox <a class="header-anchor" href="#accuratesizedbox" aria-label="Permalink to &quot;AccurateSizedBox&quot;">​</a></h3><p>Flutter 中的 SizedBox 组件会将其父组件的约束传递给其子组件，这也就意味着，如果父组件限制了最小宽度为100，即使我们通过 SizedBox 指定宽度为50，那也是没用的，<strong>因为 SizedBox的实现中会让 SizedBox 的子组件先满足 SizedBox 父组件的约束</strong>。还记得之前我们想在 AppBar 中限制 loading 组件大小的例子吗：</p><div class="language-dart vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre class="shiki github-dark vp-code-dark"><code><span class="line"><span style="color:#79B8FF;">AppBar</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">    title</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Text</span><span style="color:#E1E4E8;">(title),</span></span>
<span class="line"><span style="color:#E1E4E8;">    actions</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">&lt;</span><span style="color:#79B8FF;">Widget</span><span style="color:#F97583;">&gt;</span><span style="color:#E1E4E8;">[</span></span>
<span class="line"><span style="color:#E1E4E8;">      </span><span style="color:#79B8FF;">SizedBox</span><span style="color:#E1E4E8;">( </span><span style="color:#6A737D;">// 尝试使用SizedBox定制loading 宽高</span></span>
<span class="line"><span style="color:#E1E4E8;">        width</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">20</span><span style="color:#E1E4E8;">, </span></span>
<span class="line"><span style="color:#E1E4E8;">        height</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">20</span><span style="color:#E1E4E8;">,</span></span>
<span class="line"><span style="color:#E1E4E8;">        child</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">CircularProgressIndicator</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">          strokeWidth</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">3</span><span style="color:#E1E4E8;">,</span></span>
<span class="line"><span style="color:#E1E4E8;">          valueColor</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">AlwaysStoppedAnimation</span><span style="color:#E1E4E8;">(</span><span style="color:#79B8FF;">Colors</span><span style="color:#E1E4E8;">.white70),</span></span>
<span class="line"><span style="color:#E1E4E8;">        ),</span></span>
<span class="line"><span style="color:#E1E4E8;">      )</span></span>
<span class="line"><span style="color:#E1E4E8;">    ],</span></span>
<span class="line"><span style="color:#E1E4E8;"> )</span></span></code></pre><pre class="shiki github-light vp-code-light"><code><span class="line"><span style="color:#005CC5;">AppBar</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">    title</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Text</span><span style="color:#24292E;">(title),</span></span>
<span class="line"><span style="color:#24292E;">    actions</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">&lt;</span><span style="color:#005CC5;">Widget</span><span style="color:#D73A49;">&gt;</span><span style="color:#24292E;">[</span></span>
<span class="line"><span style="color:#24292E;">      </span><span style="color:#005CC5;">SizedBox</span><span style="color:#24292E;">( </span><span style="color:#6A737D;">// 尝试使用SizedBox定制loading 宽高</span></span>
<span class="line"><span style="color:#24292E;">        width</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">20</span><span style="color:#24292E;">, </span></span>
<span class="line"><span style="color:#24292E;">        height</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">20</span><span style="color:#24292E;">,</span></span>
<span class="line"><span style="color:#24292E;">        child</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">CircularProgressIndicator</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">          strokeWidth</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">3</span><span style="color:#24292E;">,</span></span>
<span class="line"><span style="color:#24292E;">          valueColor</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">AlwaysStoppedAnimation</span><span style="color:#24292E;">(</span><span style="color:#005CC5;">Colors</span><span style="color:#24292E;">.white70),</span></span>
<span class="line"><span style="color:#24292E;">        ),</span></span>
<span class="line"><span style="color:#24292E;">      )</span></span>
<span class="line"><span style="color:#24292E;">    ],</span></span>
<span class="line"><span style="color:#24292E;"> )</span></span></code></pre></div><p>实际结果如图14-6：</p><p><img src="/assets/4-6.77f6ab16.png" alt="图14-6"></p><p>之所以不生效，是因为父组件限制了最小高度，当然我们也可以使用 UnconstrainedBox + SizedBox 来实现我们想要的效果，但是这里我们希望通过一个组件就能搞定，为此我们自定义一个 AccurateSizedBox 组件，它和 SizedBox 的主要区别是 AccurateSizedBox 自身会遵守其父组件传递的约束<strong>而不是让其子组件去满足AccurateSizedBox 父组件的约束</strong>，具体：</p><ol><li>AccurateSizedBox 自身大小只取决于父组件的约束和用户指定的宽高。</li><li>AccurateSizedBox 确定自身大小后，限制其子组件大小。</li></ol><div class="language-dart vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre class="shiki github-dark vp-code-dark"><code><span class="line"><span style="color:#F97583;">class</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">AccurateSizedBox</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">extends</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">SingleChildRenderObjectWidget</span><span style="color:#E1E4E8;"> {</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">const</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">AccurateSizedBox</span><span style="color:#E1E4E8;">({</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#79B8FF;">Key</span><span style="color:#F97583;">?</span><span style="color:#E1E4E8;"> key,</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#79B8FF;">this</span><span style="color:#E1E4E8;">.width </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">0</span><span style="color:#E1E4E8;">,</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#79B8FF;">this</span><span style="color:#E1E4E8;">.height </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">0</span><span style="color:#E1E4E8;">,</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">required</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Widget</span><span style="color:#E1E4E8;"> child,</span></span>
<span class="line"><span style="color:#E1E4E8;">  }) </span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">super</span><span style="color:#E1E4E8;">(key</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> key, child</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> child);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">final</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">double</span><span style="color:#E1E4E8;"> width;</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">final</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">double</span><span style="color:#E1E4E8;"> height;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">@override</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">RenderObject</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">createRenderObject</span><span style="color:#E1E4E8;">(</span><span style="color:#79B8FF;">BuildContext</span><span style="color:#E1E4E8;"> context) {</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">return</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">RenderAccurateSizedBox</span><span style="color:#E1E4E8;">(width, height);</span></span>
<span class="line"><span style="color:#E1E4E8;">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">@override</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">void</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">updateRenderObject</span><span style="color:#E1E4E8;">(context, </span><span style="color:#79B8FF;">RenderAccurateSizedBox</span><span style="color:#E1E4E8;"> renderObject) {</span></span>
<span class="line"><span style="color:#E1E4E8;">    renderObject</span></span>
<span class="line"><span style="color:#E1E4E8;">      ..width </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> width</span></span>
<span class="line"><span style="color:#E1E4E8;">      ..height </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> height;</span></span>
<span class="line"><span style="color:#E1E4E8;">  }</span></span>
<span class="line"><span style="color:#E1E4E8;">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583;">class</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">RenderAccurateSizedBox</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">extends</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">RenderProxyBoxWithHitTestBehavior</span><span style="color:#E1E4E8;"> {</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">RenderAccurateSizedBox</span><span style="color:#E1E4E8;">(</span><span style="color:#79B8FF;">this</span><span style="color:#E1E4E8;">.width, </span><span style="color:#79B8FF;">this</span><span style="color:#E1E4E8;">.height);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">double</span><span style="color:#E1E4E8;"> width;</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">double</span><span style="color:#E1E4E8;"> height;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#6A737D;">// 当前组件的大小只取决于父组件传递的约束</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">@override</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">bool</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">get</span><span style="color:#E1E4E8;"> sizedByParent </span><span style="color:#F97583;">=&gt;</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">true</span><span style="color:#E1E4E8;">;</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#6A737D;">// performResize 中会调用</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">@override</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">Size</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">computeDryLayout</span><span style="color:#E1E4E8;">(</span><span style="color:#79B8FF;">BoxConstraints</span><span style="color:#E1E4E8;"> constraints) {</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">//设置当前元素宽高，遵守父组件的约束</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">return</span><span style="color:#E1E4E8;"> constraints.</span><span style="color:#B392F0;">constrain</span><span style="color:#E1E4E8;">(</span><span style="color:#79B8FF;">Size</span><span style="color:#E1E4E8;">(width, height));</span></span>
<span class="line"><span style="color:#E1E4E8;">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#6A737D;">// @override</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#6A737D;">// void performResize() {</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#6A737D;">//   // default behavior for subclasses that have sizedByParent = true</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#6A737D;">//   size = computeDryLayout(constraints);</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#6A737D;">//   assert(size.isFinite);</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#6A737D;">// }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">@override</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">void</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">performLayout</span><span style="color:#E1E4E8;">() {</span></span>
<span class="line"><span style="color:#E1E4E8;">    child</span><span style="color:#F97583;">!</span><span style="color:#E1E4E8;">.</span><span style="color:#B392F0;">layout</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">      </span><span style="color:#79B8FF;">BoxConstraints</span><span style="color:#E1E4E8;">.</span><span style="color:#B392F0;">tight</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">          </span><span style="color:#79B8FF;">Size</span><span style="color:#E1E4E8;">(</span><span style="color:#B392F0;">min</span><span style="color:#E1E4E8;">(size.width, width), </span><span style="color:#B392F0;">min</span><span style="color:#E1E4E8;">(size.height, height))),</span></span>
<span class="line"><span style="color:#E1E4E8;">      </span><span style="color:#6A737D;">// 父容器是固定大小，子元素大小改变时不影响父元素</span></span>
<span class="line"><span style="color:#E1E4E8;">      </span><span style="color:#6A737D;">// parentUseSize为false时，子组件的布局边界会是它自身，子组件布局发生变化后不会影响当前组件</span></span>
<span class="line"><span style="color:#E1E4E8;">      parentUsesSize</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">false</span><span style="color:#E1E4E8;">,</span></span>
<span class="line"><span style="color:#E1E4E8;">    );</span></span>
<span class="line"><span style="color:#E1E4E8;">  }</span></span>
<span class="line"><span style="color:#E1E4E8;">}</span></span></code></pre><pre class="shiki github-light vp-code-light"><code><span class="line"><span style="color:#D73A49;">class</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">AccurateSizedBox</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">extends</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">SingleChildRenderObjectWidget</span><span style="color:#24292E;"> {</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">const</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">AccurateSizedBox</span><span style="color:#24292E;">({</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#005CC5;">Key</span><span style="color:#D73A49;">?</span><span style="color:#24292E;"> key,</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#005CC5;">this</span><span style="color:#24292E;">.width </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">0</span><span style="color:#24292E;">,</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#005CC5;">this</span><span style="color:#24292E;">.height </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">0</span><span style="color:#24292E;">,</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">required</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Widget</span><span style="color:#24292E;"> child,</span></span>
<span class="line"><span style="color:#24292E;">  }) </span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">super</span><span style="color:#24292E;">(key</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> key, child</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> child);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">final</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">double</span><span style="color:#24292E;"> width;</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">final</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">double</span><span style="color:#24292E;"> height;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">@override</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">RenderObject</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">createRenderObject</span><span style="color:#24292E;">(</span><span style="color:#005CC5;">BuildContext</span><span style="color:#24292E;"> context) {</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">return</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">RenderAccurateSizedBox</span><span style="color:#24292E;">(width, height);</span></span>
<span class="line"><span style="color:#24292E;">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">@override</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">void</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">updateRenderObject</span><span style="color:#24292E;">(context, </span><span style="color:#005CC5;">RenderAccurateSizedBox</span><span style="color:#24292E;"> renderObject) {</span></span>
<span class="line"><span style="color:#24292E;">    renderObject</span></span>
<span class="line"><span style="color:#24292E;">      ..width </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> width</span></span>
<span class="line"><span style="color:#24292E;">      ..height </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> height;</span></span>
<span class="line"><span style="color:#24292E;">  }</span></span>
<span class="line"><span style="color:#24292E;">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;">class</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">RenderAccurateSizedBox</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">extends</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">RenderProxyBoxWithHitTestBehavior</span><span style="color:#24292E;"> {</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">RenderAccurateSizedBox</span><span style="color:#24292E;">(</span><span style="color:#005CC5;">this</span><span style="color:#24292E;">.width, </span><span style="color:#005CC5;">this</span><span style="color:#24292E;">.height);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">double</span><span style="color:#24292E;"> width;</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">double</span><span style="color:#24292E;"> height;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#6A737D;">// 当前组件的大小只取决于父组件传递的约束</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">@override</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">bool</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">get</span><span style="color:#24292E;"> sizedByParent </span><span style="color:#D73A49;">=&gt;</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">true</span><span style="color:#24292E;">;</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#6A737D;">// performResize 中会调用</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">@override</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">Size</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">computeDryLayout</span><span style="color:#24292E;">(</span><span style="color:#005CC5;">BoxConstraints</span><span style="color:#24292E;"> constraints) {</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">//设置当前元素宽高，遵守父组件的约束</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">return</span><span style="color:#24292E;"> constraints.</span><span style="color:#6F42C1;">constrain</span><span style="color:#24292E;">(</span><span style="color:#005CC5;">Size</span><span style="color:#24292E;">(width, height));</span></span>
<span class="line"><span style="color:#24292E;">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#6A737D;">// @override</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#6A737D;">// void performResize() {</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#6A737D;">//   // default behavior for subclasses that have sizedByParent = true</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#6A737D;">//   size = computeDryLayout(constraints);</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#6A737D;">//   assert(size.isFinite);</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#6A737D;">// }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">@override</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">void</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">performLayout</span><span style="color:#24292E;">() {</span></span>
<span class="line"><span style="color:#24292E;">    child</span><span style="color:#D73A49;">!</span><span style="color:#24292E;">.</span><span style="color:#6F42C1;">layout</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">      </span><span style="color:#005CC5;">BoxConstraints</span><span style="color:#24292E;">.</span><span style="color:#6F42C1;">tight</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">          </span><span style="color:#005CC5;">Size</span><span style="color:#24292E;">(</span><span style="color:#6F42C1;">min</span><span style="color:#24292E;">(size.width, width), </span><span style="color:#6F42C1;">min</span><span style="color:#24292E;">(size.height, height))),</span></span>
<span class="line"><span style="color:#24292E;">      </span><span style="color:#6A737D;">// 父容器是固定大小，子元素大小改变时不影响父元素</span></span>
<span class="line"><span style="color:#24292E;">      </span><span style="color:#6A737D;">// parentUseSize为false时，子组件的布局边界会是它自身，子组件布局发生变化后不会影响当前组件</span></span>
<span class="line"><span style="color:#24292E;">      parentUsesSize</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">false</span><span style="color:#24292E;">,</span></span>
<span class="line"><span style="color:#24292E;">    );</span></span>
<span class="line"><span style="color:#24292E;">  }</span></span>
<span class="line"><span style="color:#24292E;">}</span></span></code></pre></div><p>上面代码有三点需要注意：</p><ol><li>我们的 RenderAccurateSizedBox 不再直接继承自 RenderBox，而是继承自 RenderProxyBoxWithHitTestBehavior，RenderProxyBoxWithHitTestBehavior 是间接继承自 RenderBox的，它里面包含了默认的命中测试和绘制相关逻辑，继承自它后就不用我们再手动实现了。</li><li>我们将确定当前组件大小的逻辑挪到了computeDryLayout 方法中，因为RenderBox 的 performResize 方法会调用 computeDryLayout ，并将返回结果作为当前组件的大小。按照Flutter 框架约定，我们应该重写computeDryLayout 方法而不是 performResize 方法，就像在布局时我们应该重写 performLayout 方法而不是 layout 方法；不过，这只是一个约定，并非强制，但我们应该尽可能遵守这个约定，除非你清楚的知道自己在干什么并且能确保之后维护你代码的人也清楚。</li><li>RenderAccurateSizedBox 在调用子组件 layout 时，将 <code>parentUsesSize</code> 置为 <code>false</code>，这样的话子组件就会变成一个布局边界。</li></ol><p>下面我们测试一下：</p><div class="language-dart vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre class="shiki github-dark vp-code-dark"><code><span class="line"><span style="color:#F97583;">class</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">AccurateSizedBoxRoute</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">extends</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">StatelessWidget</span><span style="color:#E1E4E8;"> {</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">const</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">AccurateSizedBoxRoute</span><span style="color:#E1E4E8;">({</span><span style="color:#79B8FF;">Key</span><span style="color:#F97583;">?</span><span style="color:#E1E4E8;"> key}) </span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">super</span><span style="color:#E1E4E8;">(key</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> key);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">@override</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">Widget</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">build</span><span style="color:#E1E4E8;">(</span><span style="color:#79B8FF;">BuildContext</span><span style="color:#E1E4E8;"> context) {</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">final</span><span style="color:#E1E4E8;"> child </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">GestureDetector</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">      onTap</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> () </span><span style="color:#F97583;">=&gt;</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">print</span><span style="color:#E1E4E8;">(</span><span style="color:#9ECBFF;">&quot;tap&quot;</span><span style="color:#E1E4E8;">),</span></span>
<span class="line"><span style="color:#E1E4E8;">      child</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Container</span><span style="color:#E1E4E8;">(width</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">300</span><span style="color:#E1E4E8;">, height</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">300</span><span style="color:#E1E4E8;">, color</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Colors</span><span style="color:#E1E4E8;">.red),</span></span>
<span class="line"><span style="color:#E1E4E8;">    );</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">return</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Row</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">      children</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> [</span></span>
<span class="line"><span style="color:#E1E4E8;">        </span><span style="color:#79B8FF;">ConstrainedBox</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">          constraints</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">BoxConstraints</span><span style="color:#E1E4E8;">.</span><span style="color:#B392F0;">tight</span><span style="color:#E1E4E8;">(</span><span style="color:#79B8FF;">Size</span><span style="color:#E1E4E8;">(</span><span style="color:#79B8FF;">100</span><span style="color:#E1E4E8;">, </span><span style="color:#79B8FF;">100</span><span style="color:#E1E4E8;">)),</span></span>
<span class="line"><span style="color:#E1E4E8;">          child</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">SizedBox</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">            width</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">50</span><span style="color:#E1E4E8;">,</span></span>
<span class="line"><span style="color:#E1E4E8;">            height</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">50</span><span style="color:#E1E4E8;">,</span></span>
<span class="line"><span style="color:#E1E4E8;">            child</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> child,</span></span>
<span class="line"><span style="color:#E1E4E8;">          ),</span></span>
<span class="line"><span style="color:#E1E4E8;">        ),</span></span>
<span class="line"><span style="color:#E1E4E8;">        </span><span style="color:#79B8FF;">Padding</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">          padding</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">const</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">EdgeInsets</span><span style="color:#E1E4E8;">.</span><span style="color:#B392F0;">only</span><span style="color:#E1E4E8;">(left</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">8</span><span style="color:#E1E4E8;">),</span></span>
<span class="line"><span style="color:#E1E4E8;">          child</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">ConstrainedBox</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">            constraints</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">BoxConstraints</span><span style="color:#E1E4E8;">.</span><span style="color:#B392F0;">tight</span><span style="color:#E1E4E8;">(</span><span style="color:#79B8FF;">Size</span><span style="color:#E1E4E8;">(</span><span style="color:#79B8FF;">100</span><span style="color:#E1E4E8;">, </span><span style="color:#79B8FF;">100</span><span style="color:#E1E4E8;">)),</span></span>
<span class="line"><span style="color:#E1E4E8;">            child</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">AccurateSizedBox</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">              width</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">50</span><span style="color:#E1E4E8;">,</span></span>
<span class="line"><span style="color:#E1E4E8;">              height</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">50</span><span style="color:#E1E4E8;">,</span></span>
<span class="line"><span style="color:#E1E4E8;">              child</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> child,</span></span>
<span class="line"><span style="color:#E1E4E8;">            ),</span></span>
<span class="line"><span style="color:#E1E4E8;">          ),</span></span>
<span class="line"><span style="color:#E1E4E8;">        ),</span></span>
<span class="line"><span style="color:#E1E4E8;">      ],</span></span>
<span class="line"><span style="color:#E1E4E8;">    );</span></span>
<span class="line"><span style="color:#E1E4E8;">  }</span></span>
<span class="line"><span style="color:#E1E4E8;">}</span></span></code></pre><pre class="shiki github-light vp-code-light"><code><span class="line"><span style="color:#D73A49;">class</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">AccurateSizedBoxRoute</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">extends</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">StatelessWidget</span><span style="color:#24292E;"> {</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">const</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">AccurateSizedBoxRoute</span><span style="color:#24292E;">({</span><span style="color:#005CC5;">Key</span><span style="color:#D73A49;">?</span><span style="color:#24292E;"> key}) </span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">super</span><span style="color:#24292E;">(key</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> key);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">@override</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">Widget</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">build</span><span style="color:#24292E;">(</span><span style="color:#005CC5;">BuildContext</span><span style="color:#24292E;"> context) {</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">final</span><span style="color:#24292E;"> child </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">GestureDetector</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">      onTap</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> () </span><span style="color:#D73A49;">=&gt;</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">print</span><span style="color:#24292E;">(</span><span style="color:#032F62;">&quot;tap&quot;</span><span style="color:#24292E;">),</span></span>
<span class="line"><span style="color:#24292E;">      child</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Container</span><span style="color:#24292E;">(width</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">300</span><span style="color:#24292E;">, height</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">300</span><span style="color:#24292E;">, color</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Colors</span><span style="color:#24292E;">.red),</span></span>
<span class="line"><span style="color:#24292E;">    );</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">return</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Row</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">      children</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> [</span></span>
<span class="line"><span style="color:#24292E;">        </span><span style="color:#005CC5;">ConstrainedBox</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">          constraints</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">BoxConstraints</span><span style="color:#24292E;">.</span><span style="color:#6F42C1;">tight</span><span style="color:#24292E;">(</span><span style="color:#005CC5;">Size</span><span style="color:#24292E;">(</span><span style="color:#005CC5;">100</span><span style="color:#24292E;">, </span><span style="color:#005CC5;">100</span><span style="color:#24292E;">)),</span></span>
<span class="line"><span style="color:#24292E;">          child</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">SizedBox</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">            width</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">50</span><span style="color:#24292E;">,</span></span>
<span class="line"><span style="color:#24292E;">            height</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">50</span><span style="color:#24292E;">,</span></span>
<span class="line"><span style="color:#24292E;">            child</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> child,</span></span>
<span class="line"><span style="color:#24292E;">          ),</span></span>
<span class="line"><span style="color:#24292E;">        ),</span></span>
<span class="line"><span style="color:#24292E;">        </span><span style="color:#005CC5;">Padding</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">          padding</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">const</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">EdgeInsets</span><span style="color:#24292E;">.</span><span style="color:#6F42C1;">only</span><span style="color:#24292E;">(left</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">8</span><span style="color:#24292E;">),</span></span>
<span class="line"><span style="color:#24292E;">          child</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">ConstrainedBox</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">            constraints</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">BoxConstraints</span><span style="color:#24292E;">.</span><span style="color:#6F42C1;">tight</span><span style="color:#24292E;">(</span><span style="color:#005CC5;">Size</span><span style="color:#24292E;">(</span><span style="color:#005CC5;">100</span><span style="color:#24292E;">, </span><span style="color:#005CC5;">100</span><span style="color:#24292E;">)),</span></span>
<span class="line"><span style="color:#24292E;">            child</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">AccurateSizedBox</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">              width</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">50</span><span style="color:#24292E;">,</span></span>
<span class="line"><span style="color:#24292E;">              height</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">50</span><span style="color:#24292E;">,</span></span>
<span class="line"><span style="color:#24292E;">              child</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> child,</span></span>
<span class="line"><span style="color:#24292E;">            ),</span></span>
<span class="line"><span style="color:#24292E;">          ),</span></span>
<span class="line"><span style="color:#24292E;">        ),</span></span>
<span class="line"><span style="color:#24292E;">      ],</span></span>
<span class="line"><span style="color:#24292E;">    );</span></span>
<span class="line"><span style="color:#24292E;">  }</span></span>
<span class="line"><span style="color:#24292E;">}</span></span></code></pre></div><p>运行效果如图14-7：</p><p><img src="/assets/14-7.863b1558.png" alt="图14-7"></p><p>可以发现，当父组件约束子组件大小宽高是100时，我们通过 SizedBox 指定 Container 大小是为 50×50 是不能成功的， 而通过 AccurateSized 时成功了。</p><p>这里需要提醒一下读者，如果一个组件的 sizedByParent 为 true，那它在布局子组件时也是能将 <code>parentUsesSize</code> 置为 true 的，sizedByParent 为 true 表示自己是布局边界，而将 <code>parentUsesSize</code> 置为 true 或 false 决定的是子组件是否是布局边界，两者并不矛盾，这个不要混淆了。顺便提一点 Flutter 自带的 OverflowBox 组件的实现中，它的 sizedByParent 为 true，在调用子组件layout 方法时，<code>parentUsesSize</code> 传的是 true，详情读者可以查看 OverflowBox 的实现源码。</p><h2 id="_14-4-6-afterlayout" tabindex="-1">14.4.6 AfterLayout <a class="header-anchor" href="#_14-4-6-afterlayout" aria-label="Permalink to &quot;14.4.6 AfterLayout&quot;">​</a></h2><p>我们在第四章中介绍过 AfterLayout （在 9.4节 - Hero 动画 一节中也使用过它），现在我们就来看看它的实现原理。</p><p>AfterLayout 可以在布局结束后拿到子组件的代理渲染对象 （RenderAfterLayout）， RenderAfterLayout 对象会代理子组件渲染对象 ，因此，通过RenderAfterLayout 对象也就可以获取到子组件渲染对象上的属性，比如件大小、位置等。</p><p>AfterLayout 的实现代码如下：</p><div class="language-dart vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre class="shiki github-dark vp-code-dark"><code><span class="line"><span style="color:#F97583;">class</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">AfterLayout</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">extends</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">SingleChildRenderObjectWidget</span><span style="color:#E1E4E8;"> {</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">AfterLayout</span><span style="color:#E1E4E8;">({</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#79B8FF;">Key</span><span style="color:#F97583;">?</span><span style="color:#E1E4E8;"> key,</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">required</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">this</span><span style="color:#E1E4E8;">.callback,</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#79B8FF;">Widget</span><span style="color:#F97583;">?</span><span style="color:#E1E4E8;"> child,</span></span>
<span class="line"><span style="color:#E1E4E8;">  }) </span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">super</span><span style="color:#E1E4E8;">(key</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> key, child</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> child);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">@override</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">RenderObject</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">createRenderObject</span><span style="color:#E1E4E8;">(</span><span style="color:#79B8FF;">BuildContext</span><span style="color:#E1E4E8;"> context) {</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">return</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">RenderAfterLayout</span><span style="color:#E1E4E8;">(callback);</span></span>
<span class="line"><span style="color:#E1E4E8;">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">@override</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">void</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">updateRenderObject</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">      </span><span style="color:#79B8FF;">BuildContext</span><span style="color:#E1E4E8;"> context, </span><span style="color:#79B8FF;">RenderAfterLayout</span><span style="color:#E1E4E8;"> renderObject) {</span></span>
<span class="line"><span style="color:#E1E4E8;">    renderObject..callback </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> callback;</span></span>
<span class="line"><span style="color:#E1E4E8;">  }</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#6A737D;">///组件树布局结束后会被触发，注意，并不是当前组件布局结束后触发</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">final</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">ValueSetter</span><span style="color:#E1E4E8;">&lt;</span><span style="color:#79B8FF;">RenderAfterLayout</span><span style="color:#E1E4E8;">&gt; callback;</span></span>
<span class="line"><span style="color:#E1E4E8;">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583;">class</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">RenderAfterLayout</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">extends</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">RenderProxyBox</span><span style="color:#E1E4E8;"> {</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">RenderAfterLayout</span><span style="color:#E1E4E8;">(</span><span style="color:#79B8FF;">this</span><span style="color:#E1E4E8;">.callback);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">ValueSetter</span><span style="color:#E1E4E8;">&lt;</span><span style="color:#79B8FF;">RenderAfterLayout</span><span style="color:#E1E4E8;">&gt; callback;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">@override</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">void</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">performLayout</span><span style="color:#E1E4E8;">() {</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#79B8FF;">super</span><span style="color:#E1E4E8;">.</span><span style="color:#B392F0;">performLayout</span><span style="color:#E1E4E8;">();</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">// 不能直接回调callback，原因是当前组件布局完成后可能还有其他组件未完成布局</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">// 如果callback中又触发了UI更新（比如调用了 setState）则会报错。因此，我们</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">// 在 frame 结束的时候再去触发回调。</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#79B8FF;">SchedulerBinding</span><span style="color:#E1E4E8;">.instance</span></span>
<span class="line"><span style="color:#E1E4E8;">        .</span><span style="color:#B392F0;">addPostFrameCallback</span><span style="color:#E1E4E8;">((timeStamp) </span><span style="color:#F97583;">=&gt;</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">callback</span><span style="color:#E1E4E8;">(</span><span style="color:#79B8FF;">this</span><span style="color:#E1E4E8;">));</span></span>
<span class="line"><span style="color:#E1E4E8;">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#6A737D;">/// 组件在屏幕坐标中的起始点坐标（偏移）</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">Offset</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">get</span><span style="color:#E1E4E8;"> offset </span><span style="color:#F97583;">=&gt;</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">localToGlobal</span><span style="color:#E1E4E8;">(</span><span style="color:#79B8FF;">Offset</span><span style="color:#E1E4E8;">.zero);</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#6A737D;">/// 组件在屏幕上占有的矩形空间区域</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">Rect</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">get</span><span style="color:#E1E4E8;"> rect </span><span style="color:#F97583;">=&gt;</span><span style="color:#E1E4E8;"> offset </span><span style="color:#F97583;">&amp;</span><span style="color:#E1E4E8;"> size;</span></span>
<span class="line"><span style="color:#E1E4E8;">}</span></span></code></pre><pre class="shiki github-light vp-code-light"><code><span class="line"><span style="color:#D73A49;">class</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">AfterLayout</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">extends</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">SingleChildRenderObjectWidget</span><span style="color:#24292E;"> {</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">AfterLayout</span><span style="color:#24292E;">({</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#005CC5;">Key</span><span style="color:#D73A49;">?</span><span style="color:#24292E;"> key,</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">required</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">this</span><span style="color:#24292E;">.callback,</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#005CC5;">Widget</span><span style="color:#D73A49;">?</span><span style="color:#24292E;"> child,</span></span>
<span class="line"><span style="color:#24292E;">  }) </span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">super</span><span style="color:#24292E;">(key</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> key, child</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> child);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">@override</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">RenderObject</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">createRenderObject</span><span style="color:#24292E;">(</span><span style="color:#005CC5;">BuildContext</span><span style="color:#24292E;"> context) {</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">return</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">RenderAfterLayout</span><span style="color:#24292E;">(callback);</span></span>
<span class="line"><span style="color:#24292E;">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">@override</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">void</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">updateRenderObject</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">      </span><span style="color:#005CC5;">BuildContext</span><span style="color:#24292E;"> context, </span><span style="color:#005CC5;">RenderAfterLayout</span><span style="color:#24292E;"> renderObject) {</span></span>
<span class="line"><span style="color:#24292E;">    renderObject..callback </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> callback;</span></span>
<span class="line"><span style="color:#24292E;">  }</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#6A737D;">///组件树布局结束后会被触发，注意，并不是当前组件布局结束后触发</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">final</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">ValueSetter</span><span style="color:#24292E;">&lt;</span><span style="color:#005CC5;">RenderAfterLayout</span><span style="color:#24292E;">&gt; callback;</span></span>
<span class="line"><span style="color:#24292E;">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;">class</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">RenderAfterLayout</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">extends</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">RenderProxyBox</span><span style="color:#24292E;"> {</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">RenderAfterLayout</span><span style="color:#24292E;">(</span><span style="color:#005CC5;">this</span><span style="color:#24292E;">.callback);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">ValueSetter</span><span style="color:#24292E;">&lt;</span><span style="color:#005CC5;">RenderAfterLayout</span><span style="color:#24292E;">&gt; callback;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">@override</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">void</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">performLayout</span><span style="color:#24292E;">() {</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#005CC5;">super</span><span style="color:#24292E;">.</span><span style="color:#6F42C1;">performLayout</span><span style="color:#24292E;">();</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">// 不能直接回调callback，原因是当前组件布局完成后可能还有其他组件未完成布局</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">// 如果callback中又触发了UI更新（比如调用了 setState）则会报错。因此，我们</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">// 在 frame 结束的时候再去触发回调。</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#005CC5;">SchedulerBinding</span><span style="color:#24292E;">.instance</span></span>
<span class="line"><span style="color:#24292E;">        .</span><span style="color:#6F42C1;">addPostFrameCallback</span><span style="color:#24292E;">((timeStamp) </span><span style="color:#D73A49;">=&gt;</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">callback</span><span style="color:#24292E;">(</span><span style="color:#005CC5;">this</span><span style="color:#24292E;">));</span></span>
<span class="line"><span style="color:#24292E;">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#6A737D;">/// 组件在屏幕坐标中的起始点坐标（偏移）</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">Offset</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">get</span><span style="color:#24292E;"> offset </span><span style="color:#D73A49;">=&gt;</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">localToGlobal</span><span style="color:#24292E;">(</span><span style="color:#005CC5;">Offset</span><span style="color:#24292E;">.zero);</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#6A737D;">/// 组件在屏幕上占有的矩形空间区域</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">Rect</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">get</span><span style="color:#24292E;"> rect </span><span style="color:#D73A49;">=&gt;</span><span style="color:#24292E;"> offset </span><span style="color:#D73A49;">&amp;</span><span style="color:#24292E;"> size;</span></span>
<span class="line"><span style="color:#24292E;">}</span></span></code></pre></div><p>上面代码有三点需要注意：</p><ol><li><p>callback 调用时机不是在子组件完成布局后就立即调用，原因是子组件布局完成后可能还有其他组件未完成布局，如果此时调用callback，一旦 callback 中存在触发更新的代码（比如调用了 setState）则会报错。因此我们在 frame 结束的时候再去触发回调。</p></li><li><p>RenderAfterLayout 的 performLayout方法中直接调用了父类 RenderProxyBox 的 performLayout方法：</p><div class="language-dart vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre class="shiki github-dark vp-code-dark"><code><span class="line"><span style="color:#F97583;">void</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">performLayout</span><span style="color:#E1E4E8;">() {</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">if</span><span style="color:#E1E4E8;"> (child </span><span style="color:#F97583;">!=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">null</span><span style="color:#E1E4E8;">) {</span></span>
<span class="line"><span style="color:#E1E4E8;">    child</span><span style="color:#F97583;">!</span><span style="color:#E1E4E8;">.</span><span style="color:#B392F0;">layout</span><span style="color:#E1E4E8;">(constraints, parentUsesSize</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">true</span><span style="color:#E1E4E8;">);</span></span>
<span class="line"><span style="color:#E1E4E8;">    size </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> child</span><span style="color:#F97583;">!</span><span style="color:#E1E4E8;">.size;</span></span>
<span class="line"><span style="color:#E1E4E8;">  } </span><span style="color:#F97583;">else</span><span style="color:#E1E4E8;"> {</span></span>
<span class="line"><span style="color:#E1E4E8;">    size </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">computeSizeForNoChild</span><span style="color:#E1E4E8;">(constraints);</span></span>
<span class="line"><span style="color:#E1E4E8;">  }</span></span>
<span class="line"><span style="color:#E1E4E8;">}</span></span></code></pre><pre class="shiki github-light vp-code-light"><code><span class="line"><span style="color:#D73A49;">void</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">performLayout</span><span style="color:#24292E;">() {</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">if</span><span style="color:#24292E;"> (child </span><span style="color:#D73A49;">!=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">null</span><span style="color:#24292E;">) {</span></span>
<span class="line"><span style="color:#24292E;">    child</span><span style="color:#D73A49;">!</span><span style="color:#24292E;">.</span><span style="color:#6F42C1;">layout</span><span style="color:#24292E;">(constraints, parentUsesSize</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">true</span><span style="color:#24292E;">);</span></span>
<span class="line"><span style="color:#24292E;">    size </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> child</span><span style="color:#D73A49;">!</span><span style="color:#24292E;">.size;</span></span>
<span class="line"><span style="color:#24292E;">  } </span><span style="color:#D73A49;">else</span><span style="color:#24292E;"> {</span></span>
<span class="line"><span style="color:#24292E;">    size </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">computeSizeForNoChild</span><span style="color:#24292E;">(constraints);</span></span>
<span class="line"><span style="color:#24292E;">  }</span></span>
<span class="line"><span style="color:#24292E;">}</span></span></code></pre></div><p>可以看到是直接将父组件传给自身的约束传递给子组件，并将子组件的大小设置为自身大小。也就是说 RenderAfterLayout 的大小和其子组件大小是相同的</p></li><li><p>我们定义了 offset 和 rect 两个属性，它们是组件相对于屏幕的位置偏移和占用的矩形空间范围。但是实战中，我们经常需要获取的是子组件相对于某个父级组件的坐标和矩形空间范围，这时候我们可以调用 RenderObject 的<code>localToGlobal</code> 方法，比如下面的代码展示了Stack中某个子组件获取相对于Stack 的矩形空间范围：</p><div class="language-dart vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre class="shiki github-dark vp-code-dark"><code><span class="line"><span style="color:#E1E4E8;">...</span></span>
<span class="line"><span style="color:#79B8FF;">Widget</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">build</span><span style="color:#E1E4E8;">(context){</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">return</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Stack</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">    alignment</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">AlignmentDirectional</span><span style="color:#E1E4E8;">.topCenter,</span></span>
<span class="line"><span style="color:#E1E4E8;">    children</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> [</span></span>
<span class="line"><span style="color:#E1E4E8;">      </span><span style="color:#79B8FF;">AfterLayout</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">        callback</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> (renderAfterLayout){</span></span>
<span class="line"><span style="color:#E1E4E8;">         </span><span style="color:#6A737D;">//我们需要获取的是AfterLayout子组件相对于Stack的Rect</span></span>
<span class="line"><span style="color:#E1E4E8;">         _rect </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> renderAfterLayout.</span><span style="color:#B392F0;">localToGlobal</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">            </span><span style="color:#79B8FF;">Offset</span><span style="color:#E1E4E8;">.zero,</span></span>
<span class="line"><span style="color:#E1E4E8;">            </span><span style="color:#6A737D;">//找到 Stack 对应的 RenderObject 对象</span></span>
<span class="line"><span style="color:#E1E4E8;">            ancestor</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> context.</span><span style="color:#B392F0;">findRenderObject</span><span style="color:#E1E4E8;">(),</span></span>
<span class="line"><span style="color:#E1E4E8;">          ) </span><span style="color:#F97583;">&amp;</span><span style="color:#E1E4E8;"> renderAfterLayout.size;</span></span>
<span class="line"><span style="color:#E1E4E8;">        },</span></span>
<span class="line"><span style="color:#E1E4E8;">        child</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Text</span><span style="color:#E1E4E8;">(</span><span style="color:#9ECBFF;">&#39;Flutter@wendux&#39;</span><span style="color:#E1E4E8;">),</span></span>
<span class="line"><span style="color:#E1E4E8;">      ),</span></span>
<span class="line"><span style="color:#E1E4E8;">    ]</span></span>
<span class="line"><span style="color:#E1E4E8;">  );</span></span>
<span class="line"><span style="color:#E1E4E8;">}</span></span></code></pre><pre class="shiki github-light vp-code-light"><code><span class="line"><span style="color:#24292E;">...</span></span>
<span class="line"><span style="color:#005CC5;">Widget</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">build</span><span style="color:#24292E;">(context){</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">return</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Stack</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">    alignment</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">AlignmentDirectional</span><span style="color:#24292E;">.topCenter,</span></span>
<span class="line"><span style="color:#24292E;">    children</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> [</span></span>
<span class="line"><span style="color:#24292E;">      </span><span style="color:#005CC5;">AfterLayout</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">        callback</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> (renderAfterLayout){</span></span>
<span class="line"><span style="color:#24292E;">         </span><span style="color:#6A737D;">//我们需要获取的是AfterLayout子组件相对于Stack的Rect</span></span>
<span class="line"><span style="color:#24292E;">         _rect </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> renderAfterLayout.</span><span style="color:#6F42C1;">localToGlobal</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">            </span><span style="color:#005CC5;">Offset</span><span style="color:#24292E;">.zero,</span></span>
<span class="line"><span style="color:#24292E;">            </span><span style="color:#6A737D;">//找到 Stack 对应的 RenderObject 对象</span></span>
<span class="line"><span style="color:#24292E;">            ancestor</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> context.</span><span style="color:#6F42C1;">findRenderObject</span><span style="color:#24292E;">(),</span></span>
<span class="line"><span style="color:#24292E;">          ) </span><span style="color:#D73A49;">&amp;</span><span style="color:#24292E;"> renderAfterLayout.size;</span></span>
<span class="line"><span style="color:#24292E;">        },</span></span>
<span class="line"><span style="color:#24292E;">        child</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Text</span><span style="color:#24292E;">(</span><span style="color:#032F62;">&#39;Flutter@wendux&#39;</span><span style="color:#24292E;">),</span></span>
<span class="line"><span style="color:#24292E;">      ),</span></span>
<span class="line"><span style="color:#24292E;">    ]</span></span>
<span class="line"><span style="color:#24292E;">  );</span></span>
<span class="line"><span style="color:#24292E;">}</span></span></code></pre></div></li></ol><h2 id="_14-4-7-再论-constraints" tabindex="-1">14.4.7 再论 Constraints <a class="header-anchor" href="#_14-4-7-再论-constraints" aria-label="Permalink to &quot;14.4.7 再论 Constraints&quot;">​</a></h2><p>Constraints（约束）主要描述了最小和最大宽高的限制，理解组件在布局过程中如何根据约束确定自身或子节点的大小对我们理解组件的布局行为有很大帮助，现在我们就通过一个实现 200*200 的红色 Container 的例子来说明。为了排除干扰，我们让根节点（RenderView）作为 Container 的父组件，我们的代码是：</p><div class="language-dart vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre class="shiki github-dark vp-code-dark"><code><span class="line"><span style="color:#79B8FF;">Container</span><span style="color:#E1E4E8;">(width</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">200</span><span style="color:#E1E4E8;">, height</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">200</span><span style="color:#E1E4E8;">, color</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Colors</span><span style="color:#E1E4E8;">.red)</span></span></code></pre><pre class="shiki github-light vp-code-light"><code><span class="line"><span style="color:#005CC5;">Container</span><span style="color:#24292E;">(width</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">200</span><span style="color:#24292E;">, height</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">200</span><span style="color:#24292E;">, color</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Colors</span><span style="color:#24292E;">.red)</span></span></code></pre></div><p>但实际运行之后，你会发现整个屏幕都变成了红色！为什么呢？我们看看 RenderView 的布局实现：</p><div class="language-dart vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre class="shiki github-dark vp-code-dark"><code><span class="line"><span style="color:#F97583;">@override</span></span>
<span class="line"><span style="color:#F97583;">void</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">performLayout</span><span style="color:#E1E4E8;">() {</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#6A737D;">//configuration.size 为当前设备屏幕</span></span>
<span class="line"><span style="color:#E1E4E8;">  _size </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> configuration.size; </span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">if</span><span style="color:#E1E4E8;"> (child </span><span style="color:#F97583;">!=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">null</span><span style="color:#E1E4E8;">)</span></span>
<span class="line"><span style="color:#E1E4E8;">    child</span><span style="color:#F97583;">!</span><span style="color:#E1E4E8;">.</span><span style="color:#B392F0;">layout</span><span style="color:#E1E4E8;">(</span><span style="color:#79B8FF;">BoxConstraints</span><span style="color:#E1E4E8;">.</span><span style="color:#B392F0;">tight</span><span style="color:#E1E4E8;">(_size)); </span><span style="color:#6A737D;">//强制子组件和屏幕一样大</span></span>
<span class="line"><span style="color:#E1E4E8;">}</span></span></code></pre><pre class="shiki github-light vp-code-light"><code><span class="line"><span style="color:#D73A49;">@override</span></span>
<span class="line"><span style="color:#D73A49;">void</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">performLayout</span><span style="color:#24292E;">() {</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#6A737D;">//configuration.size 为当前设备屏幕</span></span>
<span class="line"><span style="color:#24292E;">  _size </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> configuration.size; </span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">if</span><span style="color:#24292E;"> (child </span><span style="color:#D73A49;">!=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">null</span><span style="color:#24292E;">)</span></span>
<span class="line"><span style="color:#24292E;">    child</span><span style="color:#D73A49;">!</span><span style="color:#24292E;">.</span><span style="color:#6F42C1;">layout</span><span style="color:#24292E;">(</span><span style="color:#005CC5;">BoxConstraints</span><span style="color:#24292E;">.</span><span style="color:#6F42C1;">tight</span><span style="color:#24292E;">(_size)); </span><span style="color:#6A737D;">//强制子组件和屏幕一样大</span></span>
<span class="line"><span style="color:#24292E;">}</span></span></code></pre></div><p>这里需要介绍一下两种常用的约束：</p><ol><li>宽松约束：不限制最小宽高（为0），只限制最大宽高，可以通过 <code>BoxConstraints.loose(Size size)</code> 来快速创建。</li><li>严格约束：限制为固定大小；即最小宽度等于最大宽度，最小高度等于最大高度，可以通过 <code>BoxConstraints.tight(Size size)</code> 来快速创建。</li></ol><p>可以发现，RenderView 中给子组件传递的是一个严格约束，即强制子组件大小等于屏幕大小，所以 Container 便撑满了屏幕。那我们怎么才能让指定的大小生效呢？标准答案就是<strong>引入一个中间组件，让这个中间组件遵守父组件的约束，然后对子组件传递新的约束</strong>。对于这个例子来讲，最简单的方式是用一个 Align 组件来包裹 Container：</p><div class="language-dart vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre class="shiki github-dark vp-code-dark"><code><span class="line"><span style="color:#F97583;">@override</span></span>
<span class="line"><span style="color:#79B8FF;">Widget</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">build</span><span style="color:#E1E4E8;">(</span><span style="color:#79B8FF;">BuildContext</span><span style="color:#E1E4E8;"> context) {</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">var</span><span style="color:#E1E4E8;"> container </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Container</span><span style="color:#E1E4E8;">(width</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">200</span><span style="color:#E1E4E8;">, height</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">200</span><span style="color:#E1E4E8;">, color</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Colors</span><span style="color:#E1E4E8;">.red);</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">return</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Align</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">    child</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> container,</span></span>
<span class="line"><span style="color:#E1E4E8;">    alignment</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Alignment</span><span style="color:#E1E4E8;">.topLeft,</span></span>
<span class="line"><span style="color:#E1E4E8;">  );</span></span>
<span class="line"><span style="color:#E1E4E8;">}</span></span></code></pre><pre class="shiki github-light vp-code-light"><code><span class="line"><span style="color:#D73A49;">@override</span></span>
<span class="line"><span style="color:#005CC5;">Widget</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">build</span><span style="color:#24292E;">(</span><span style="color:#005CC5;">BuildContext</span><span style="color:#24292E;"> context) {</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">var</span><span style="color:#24292E;"> container </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Container</span><span style="color:#24292E;">(width</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">200</span><span style="color:#24292E;">, height</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">200</span><span style="color:#24292E;">, color</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Colors</span><span style="color:#24292E;">.red);</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">return</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Align</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">    child</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> container,</span></span>
<span class="line"><span style="color:#24292E;">    alignment</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Alignment</span><span style="color:#24292E;">.topLeft,</span></span>
<span class="line"><span style="color:#24292E;">  );</span></span>
<span class="line"><span style="color:#24292E;">}</span></span></code></pre></div><p>Align 会遵守 RenderView 的约束，让自身撑满屏幕，然后会给子组件传递一个宽松约束（最小宽高为0，最大宽高为200），这样 Container 就可以变成 200 * 200 了。</p><p>当然我们还可以使用其他组件来代替 Align，比如 UnconstrainedBox，但原理是相同的，读者可以查看源码验证。</p><h2 id="_14-4-8-总结" tabindex="-1">14.4.8 总结 <a class="header-anchor" href="#_14-4-8-总结" aria-label="Permalink to &quot;14.4.8 总结&quot;">​</a></h2><p>通过本节，相信你已经对 flutter 的布局流程熟悉了，现在我们看一张 flutter 官网的图（图14-8）：</p><p><img src="/assets/14-8.064a10af.png" alt="图14-8"></p><p>现在我们再来看一下官网关于Flutter布局的解释：</p><blockquote><p>“ 在进行布局的时候，Flutter 会以 DFS（深度优先遍历）方式遍历渲染树，并 <strong>将限制以自上而下的方式</strong> 从父节点传递给子节点。子节点若要确定自己的大小，则 <strong>必须</strong> 遵循父节点传递的限制。子节点的响应方式是在父节点建立的约束内 <strong>将大小以自下而上的方式</strong> 传递给父节点。”</p></blockquote><p>是不是理解的更透彻了一些！</p></div></div></main><footer class="VPDocFooter" data-v-6b87e69f data-v-ef5dee53><!--[--><!--]--><div class="edit-info" data-v-ef5dee53><div class="edit-link" data-v-ef5dee53><a class="VPLink link vp-external-link-icon no-icon edit-link-button" href="https://gitee.com/luoguangguang/note/tree/master/docs/largeFrontEnd/flutter/chapter14/layout.md" target="_blank" rel="noreferrer" data-v-ef5dee53><!--[--><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="edit-link-icon" aria-label="edit icon" data-v-ef5dee53><path d="M18,23H4c-1.7,0-3-1.3-3-3V6c0-1.7,1.3-3,3-3h7c0.6,0,1,0.4,1,1s-0.4,1-1,1H4C3.4,5,3,5.4,3,6v14c0,0.6,0.4,1,1,1h14c0.6,0,1-0.4,1-1v-7c0-0.6,0.4-1,1-1s1,0.4,1,1v7C21,21.7,19.7,23,18,23z"></path><path d="M8,17c-0.3,0-0.5-0.1-0.7-0.3C7,16.5,6.9,16.1,7,15.8l1-4c0-0.2,0.1-0.3,0.3-0.5l9.5-9.5c1.2-1.2,3.2-1.2,4.4,0c1.2,1.2,1.2,3.2,0,4.4l-9.5,9.5c-0.1,0.1-0.3,0.2-0.5,0.3l-4,1C8.2,17,8.1,17,8,17zM9.9,12.5l-0.5,2.1l2.1-0.5l9.3-9.3c0.4-0.4,0.4-1.1,0-1.6c-0.4-0.4-1.2-0.4-1.6,0l0,0L9.9,12.5z M18.5,2.5L18.5,2.5L18.5,2.5z"></path></svg> 在Gitee上参与编辑此页<!--]--></a></div><!----></div><nav class="prev-next" data-v-ef5dee53><div class="pager" data-v-ef5dee53><!----></div><div class="pager" data-v-ef5dee53><a class="pager-link next" href="./largeFrontEnd/flutter/chapter1/index.html" data-v-ef5dee53><span class="desc" data-v-ef5dee53>Next page</span><span class="title" data-v-ef5dee53>第一章 初识Flutter</span></a></div></nav></footer><!--[--><!--]--></div></div></div><!--[--><!--]--></div></div><footer class="VPFooter has-sidebar" data-v-1919c326 data-v-e03eb2e1><div class="container" data-v-e03eb2e1><p class="message" data-v-e03eb2e1>Released under the MIT License.</p><p class="copyright" data-v-e03eb2e1>Copyright © 2023-present LG of GZU</p></div></footer><!--[--><!--]--></div></div>
    <script>window.__VP_HASH_MAP__=JSON.parse("{\"backend_index.md\":\"0cb07c7c\",\"largefrontend_electron_index.md\":\"adab8e13\",\"largefrontend_flutter_chapter1_index.md\":\"5106cc23\",\"index.md\":\"20a66851\",\"largefrontend_flutter_chapter10_intro.md\":\"5756d1e8\",\"largefrontend_flutter_chapter10_index.md\":\"87e47f16\",\"largefrontend_flutter_chapter1_flutter_intro.md\":\"9ad5fc78\",\"largefrontend_flutter_chapter12_package_and_plugin.md\":\"c2f14cd7\",\"largefrontend_flutter_chapter12_flutter_web.md\":\"f4b6b743\",\"largefrontend_flutter_chapter10_combine.md\":\"333319d8\",\"largefrontend_flutter_chapter14_flutter_ui_system.md\":\"a1011f7b\",\"largefrontend_flutter_chapter11_index.md\":\"35dcdd7c\",\"largefrontend_flutter_chapter12_index.md\":\"890b799c\",\"largefrontend_flutter_chapter13_index.md\":\"6db9b579\",\"largefrontend_flutter_chapter14_index.md\":\"87f7da6b\",\"largefrontend_flutter_chapter1_mobile_development_intro.md\":\"40c15ac2\",\"largefrontend_flutter_chapter10_turn_box.md\":\"6a792fe1\",\"largefrontend_flutter_chapter3_index.md\":\"bc1d0de5\",\"largefrontend_flutter_chapter4_stack.md\":\"3f5c3186\",\"largefrontend_flutter_chapter1_install_flutter.md\":\"4543c368\",\"largefrontend_flutter_chapter14_paint.md\":\"8f7289b0\",\"largefrontend_flutter_chapter5_padding.md\":\"9fadce05\",\"largefrontend_flutter_chapter4_index.md\":\"f9b298f7\",\"largefrontend_flutter_chapter2_flutter_package_mgr.md\":\"4a3b7161\",\"largefrontend_flutter_chapter11_socket.md\":\"e8264e44\",\"largefrontend_flutter_chapter4_intro.md\":\"1341b5fd\",\"largefrontend_flutter_chapter14_update.md\":\"e4d2c2f0\",\"largefrontend_flutter_chapter5_index.md\":\"455baa0a\",\"largefrontend_flutter_chapter4_alignment.md\":\"a6596610\",\"largefrontend_flutter_chapter13_multi_languages_support.md\":\"c113238d\",\"largefrontend_flutter_chapter11_dio.md\":\"64f4e813\",\"largefrontend_flutter_chapter2_first_flutter_app.md\":\"b650181d\",\"largefrontend_flutter_chapter2_flutter_app_debug.md\":\"67a72433\",\"largefrontend_flutter_chapter2_state_manage.md\":\"9bb80d37\",\"largefrontend_flutter_chapter6_index.md\":\"5a321b81\",\"largefrontend_flutter_chapter5_material_scaffold.md\":\"8e6b896a\",\"largefrontend_flutter_chapter3_buttons.md\":\"ef950360\",\"largefrontend_flutter_chapter10_done_widget.md\":\"c3b44318\",\"largefrontend_flutter_chapter6_single_child_scrollview.md\":\"e3f75f3b\",\"largefrontend_flutter_chapter14_paint_flow.md\":\"d2f8102a\",\"largefrontend_flutter_chapter6_intro.md\":\"125a064e\",\"largefrontend_flutter_chapter5_transform.md\":\"22708e1e\",\"largefrontend_flutter_chapter14_layer.md\":\"4ed449fc\",\"largefrontend_flutter_chapter11_websocket.md\":\"0a022e47\",\"largefrontend_flutter_chapter5_fittedbox.md\":\"db915726\",\"largefrontend_flutter_chapter2_index.md\":\"d8e937b7\",\"largefrontend_flutter_chapter3_radio_and_checkbox.md\":\"025aba21\",\"largefrontend_flutter_chapter13_locallization_implement.md\":\"dc4b83fe\",\"largefrontend_flutter_chapter2_thread_model_and_error_report.md\":\"a29199d6\",\"largefrontend_flutter_chapter5_decoratedbox.md\":\"20f093ba\",\"largefrontend_flutter_chapter14_render_object.md\":\"55a1b551\",\"largefrontend_flutter_chapter14_element_buildcontext.md\":\"4272eae4\",\"largefrontend_flutter_chapter4_wrap_and_flow.md\":\"ac41b5bd\",\"largefrontend_flutter_chapter11_file_operation.md\":\"996a3a5d\",\"largefrontend_flutter_chapter13_faq.md\":\"ba7b9c70\",\"largefrontend_webfrontend_md_10.md\":\"51e0a312\",\"largefrontend_webfrontend_md_24.md\":\"0a4abd08\",\"largefrontend_flutter_chapter14_flutter_app_startup.md\":\"9b5942a1\",\"largefrontend_flutter_chapter10_custom_paint.md\":\"8b2a0a46\",\"largefrontend_flutter_chapter2_flutter_assets_mgr.md\":\"0e38b701\",\"largefrontend_interview_01.md\":\"2ac4b56d\",\"largefrontend_flutter_chapter8_index.md\":\"e83d3d80\",\"largefrontend_webfrontend_md_16.md\":\"a5610416\",\"largefrontend_flutter_chapter9_index.md\":\"3fb342a7\",\"largefrontend_flutter_chapter8_listener.md\":\"1b9b0316\",\"largefrontend_webfrontend_md_37.md\":\"3cacf3a2\",\"largefrontend_flutter_chapter4_layoutbuilder.md\":\"3d4bf94b\",\"largefrontend_webfrontend_md_17.md\":\"ee74edfb\",\"largefrontend_webfrontend_md_13.md\":\"b180bb5b\",\"largefrontend_flutter_chapter7_index.md\":\"0eb456b2\",\"largefrontend_webfrontend_md_31.md\":\"309453c7\",\"largefrontend_webfrontend_md_25.md\":\"95fdb4fe\",\"largefrontend_webfrontend_md_12.md\":\"3b14e491\",\"largefrontend_webfrontend_md_23.md\":\"18f08d72\",\"largefrontend_flutter_chapter11_json_model.md\":\"c84d2ccf\",\"largefrontend_webfrontend_md_01.md\":\"fbb7a35f\",\"largefrontend_flutter_chapter3_progress.md\":\"9bc148d3\",\"largefrontend_webfrontend_md_18.md\":\"ee651cc6\",\"largefrontend_flutter_sponsor.md\":\"e71431a9\",\"largefrontend_webfrontend_md_36.md\":\"f22f6d0d\",\"largefrontend_webfrontend_md_07.md\":\"71c2ad1d\",\"largefrontend_webfrontend_md_19.md\":\"e7907728\",\"largefrontend_webfrontend_md_20.md\":\"5a68bb4f\",\"largefrontend_webfrontend_md_32.md\":\"91d2f39f\",\"largefrontend_flutter_chapter7_futurebuilder_and_streambuilder.md\":\"af3535c0\",\"largefrontend_flutter_chapter9_hero.md\":\"25b46cf2\",\"largefrontend_flutter_chapter8_notification.md\":\"909f6756\",\"largefrontend_webfrontend_md_08.md\":\"0d59dce4\",\"largefrontend_flutter_chapter3_img_and_icon.md\":\"8b409b55\",\"largefrontend_webfrontend_md_04.md\":\"d6c15d55\",\"largefrontend_flutter_chapter9_animated_switcher.md\":\"9c0b3119\",\"largefrontend_webfrontend_md_22.md\":\"7d6fea68\",\"largefrontend_flutter_chapter2_flutter_widget_intro.md\":\"13414fea\",\"largefrontend_flutter_chapter13_intl.md\":\"d4776610\",\"largefrontend_overview_index.md\":\"3c87f69c\",\"largefrontend_flutter_chapter5_container.md\":\"373cc694\",\"largefrontend_wechatminiprogram_index.md\":\"7f40708d\",\"largefrontend_webfrontend_md_38.md\":\"3b6bb332\",\"linux_index.md\":\"624ecc66\",\"largefrontend_webfrontend_md_35.md\":\"f0538423\",\"myteam_index.md\":\"91a2c11f\",\"largefrontend_flutter_chapter6_pageview.md\":\"36fcbc65\",\"largefrontend_webfrontend_md_03.md\":\"2e5087f0\",\"largefrontend_webfrontend_md_21.md\":\"c181316e\",\"largefrontend_flutter_chapter3_input_and_form.md\":\"e4fba9dd\",\"largefrontend_flutter_chapter14_compositing.md\":\"6d96f932\",\"largefrontend_webfrontend_md_29.md\":\"c6fada7d\",\"largefrontend_flutter_chapter4_constraints.md\":\"9e4baace\",\"largefrontend_webfrontend_md_11.md\":\"63cd9c49\",\"largefrontend_webfrontend_md_14.md\":\"1423aeb1\",\"largefrontend_flutter_chapter6_tabview.md\":\"56048fb1\",\"largefrontend_webfrontend_md_33.md\":\"fdbe86fa\",\"largefrontend_flutter_chapter6_nestedscrollview.md\":\"2a66b86a\",\"largefrontend_webfrontend_md_34.md\":\"ad8ea220\",\"largefrontend_flutter_chapter2_flutter_router.md\":\"bb6e776c\",\"largefrontend_webfrontend_md_30.md\":\"cd2cb5aa\",\"largefrontend_flutter_chapter4_flex.md\":\"ebd43d6a\",\"largefrontend_webfrontend_md_09.md\":\"eedafcb1\",\"largefrontend_webfrontend_md_40.md\":\"4d4fefa1\",\"largefrontend_flutter_join_us.md\":\"44c81d86\",\"largefrontend_flutter_chapter9_route_transition.md\":\"f56cf837\",\"largefrontend_uniapp_index.md\":\"fa387c39\",\"largefrontend_flutter_chapter4_row_and_column.md\":\"964229f5\",\"largefrontend_flutter_chapter9_stagger_animation.md\":\"315538f5\",\"largefrontend_flutter_chapter3_text.md\":\"6720230c\",\"largefrontend_flutter_chapter10_watermark.md\":\"2e4ddf03\",\"largefrontend_flutter_chapter11_http.md\":\"efd2ee64\",\"largefrontend_webfrontend_md_06.md\":\"e20b049a\",\"largefrontend_flutter_summary.md\":\"2e31b0a8\",\"largefrontend_flutter_chapter6_animatedlist.md\":\"20d9436f\",\"largefrontend_flutter_chapter5_clip.md\":\"91f4174c\",\"largefrontend_webfrontend_md_02.md\":\"40cd2099\",\"largefrontend_flutter_chapter6_gridview.md\":\"b3be7e60\",\"largefrontend_flutter_chapter6_custom_scrollview.md\":\"01be950c\",\"largefrontend_flutter_chapter11_download_with_chunks.md\":\"9379cc89\",\"largefrontend_flutter_chapter6_scroll_controller.md\":\"ffa2d5fd\",\"largefrontend_flutter_chapter9_intro.md\":\"3fc29b5f\",\"largefrontend_flutter_chapter1_dart.md\":\"6bba4e7c\",\"largefrontend_flutter_chapter7_inherited_widget.md\":\"5a21d704\",\"largefrontend_flutter_index.md\":\"cd81fbbe\",\"largefrontend_flutter_reference.md\":\"36336375\",\"largefrontend_flutter_chapter6_keepalive.md\":\"535137be\",\"largefrontend_flutter_chapter14_layout.md\":\"0d7a8df3\",\"largefrontend_flutter_chapter6_sliver.md\":\"0c8b7553\",\"largefrontend_webfrontend_md_39.md\":\"aef49a57\",\"largefrontend_webfrontend_md_15.md\":\"bf9ff1a1\",\"largefrontend_flutter_chapter14_image_and_cache.md\":\"9622c56b\",\"largefrontend_webfrontend_md_28.md\":\"83169d10\",\"largefrontend_flutter_chapter8_eventbus.md\":\"fc0db86d\",\"largefrontend_flutter_chapter7_value_listenable_builder.md\":\"a0c76121\",\"largefrontend_flutter_preface.md\":\"604b9934\",\"largefrontend_flutter_chapter7_theme.md\":\"c21036bc\",\"largefrontend_webfrontend_md_05.md\":\"f7607009\",\"largefrontend_flutter_chapter6_listview.md\":\"472910bd\",\"largefrontend_flutter_chapter7_provider.md\":\"ab590744\",\"largefrontend_flutter_chapter8_gesture.md\":\"859bbb38\",\"largefrontend_flutter_chapter9_animated_widgets.md\":\"e8cd3c4d\",\"largefrontend_webfrontend_md_26.md\":\"4928006b\",\"largefrontend_flutter_chapter10_gradient_circular_progress_demo.md\":\"b3d48a2c\",\"largefrontend_flutter_chapter8_hittest.md\":\"e963812b\",\"largefrontend_flutter_chapter7_willpopscope.md\":\"d224ecce\",\"largefrontend_flutter_chapter9_animation_structure.md\":\"672eb3c0\",\"largefrontend_flutter_chapter8_gesture_conflict.md\":\"15bbadba\",\"largefrontend_webfrontend_md_27.md\":\"0b5b5e39\",\"largefrontend_flutter_chapter7_dailog.md\":\"5f91e51a\",\"largefrontend_flutter_chapter10_custom_checkbox.md\":\"0db7d50f\"}");window.__VP_SITE_DATA__=JSON.parse("{\"lang\":\"zh-CN\",\"dir\":\"ltr\",\"title\":\"落光的Pro博客\",\"description\":\"Vite & Vue powered static site generator.\",\"base\":\"./\",\"head\":[],\"appearance\":true,\"themeConfig\":{\"search\":{\"provider\":\"local\",\"options\":{\"locales\":{\"zh\":{\"translations\":{\"button\":{\"buttonText\":\"搜索文档\",\"buttonAriaLabel\":\"搜索文档\"},\"modal\":{\"noResultsText\":\"无法找到相关结果\",\"resetButtonTitle\":\"清除查询条件\",\"footer\":{\"selectText\":\"选择\",\"navigateText\":\"切换\"}}}}}}},\"logo\":\"/icon-radius.png\",\"nav\":[{\"text\":\"大前端\",\"items\":[{\"text\":\"Web前端\",\"link\":\"/largeFrontEnd/webFrontEnd/md/01.md\"},{\"text\":\"Uni-app\",\"link\":\"/largeFrontEnd/uniapp/\"},{\"text\":\"微信小程序\",\"link\":\"/largeFrontEnd/weChatMiniProgram/\"},{\"text\":\"Electron\",\"link\":\"/largeFrontEnd/electron/\"},{\"text\":\"Flutter\",\"link\":\"/largeFrontEnd/flutter/\"},{\"text\":\"前端面试题\",\"link\":\"/largeFrontEnd/interview/01.md\"}]},{\"text\":\"后端\",\"link\":\"/backEnd/\"},{\"text\":\"Linux\",\"link\":\"/linux/\"},{\"text\":\"我的团队\",\"link\":\"/myTeam/\"}],\"sidebar\":{\"/largeFrontEnd/webFrontEnd/\":[{\"text\":\"第一阶段  基础入门\",\"collapsed\":true,\"items\":[{\"text\":\"一、基础认知\",\"link\":\"/largeFrontEnd/webFrontEnd/md/01.md\"},{\"text\":\"二、HTML标签学习\",\"link\":\"/largeFrontEnd/webFrontEnd/md/02.md\"},{\"text\":\"三、HTML基础\",\"link\":\"/largeFrontEnd/webFrontEnd/md/03.md\"},{\"text\":\"四、CSS基础\",\"link\":\"/largeFrontEnd/webFrontEnd/md/04.md\"},{\"text\":\"五、CSS进阶\",\"link\":\"/largeFrontEnd/webFrontEnd/md/05.md\"},{\"text\":\"六、盒子模型\",\"link\":\"/largeFrontEnd/webFrontEnd/md/06.md\"},{\"text\":\"七、CSS浮动\",\"link\":\"/largeFrontEnd/webFrontEnd/md/07.md\"},{\"text\":\"八、CSS定位装饰\",\"link\":\"/largeFrontEnd/webFrontEnd/md/08.md\"},{\"text\":\"九、CSS精灵图\",\"link\":\"/largeFrontEnd/webFrontEnd/md/09.md\"},{\"text\":\"项目前置认知\",\"link\":\"/largeFrontEnd/webFrontEnd/md/10.md\"},{\"text\":\"项目结构搭建\",\"link\":\"/largeFrontEnd/webFrontEnd/md/11.md\"},{\"text\":\"字体图标、动画\",\"link\":\"/largeFrontEnd/webFrontEnd/md/12.md\"},{\"text\":\"移动web开发\",\"link\":\"/largeFrontEnd/webFrontEnd/md/13.md\"}]},{\"text\":\"第二阶段 技术进阶\",\"collapsed\":true,\"items\":[{\"text\":\"JavaScript深入浅出\",\"link\":\"/largeFrontEnd/webFrontEnd/md/14.md\"},{\"text\":\"JavaScript核心之web APIs\",\"link\":\"/largeFrontEnd/webFrontEnd/md/15.md\"},{\"text\":\"一、Web APIS导读\",\"link\":\"/largeFrontEnd/webFrontEnd/md/16.md\"},{\"text\":\"二、DOM导读\",\"link\":\"/largeFrontEnd/webFrontEnd/md/17.md\"},{\"text\":\"三、事件高级导读\",\"link\":\"/largeFrontEnd/webFrontEnd/md/18.md\"},{\"text\":\"四、BOM导读\",\"link\":\"/largeFrontEnd/webFrontEnd/md/19.md\"},{\"text\":\"五、PC网页特效导读\",\"link\":\"/largeFrontEnd/webFrontEnd/md/20.md\"},{\"text\":\"六、移动端特效导读\",\"link\":\"/largeFrontEnd/webFrontEnd/md/21.md\"},{\"text\":\"七、本地存储导读\",\"link\":\"/largeFrontEnd/webFrontEnd/md/22.md\"},{\"text\":\"jQuery入门导读\",\"link\":\"/largeFrontEnd/webFrontEnd/md/23.md\"},{\"text\":\"数据可视化项目导读\",\"link\":\"/largeFrontEnd/webFrontEnd/md/24.md\"}]},{\"text\":\"Node.js\",\"collapsed\":true,\"items\":[{\"text\":\"buffer(缓冲器)\",\"link\":\"/largeFrontEnd/webFrontEnd/md/25.md\"},{\"text\":\"计算机基础\",\"link\":\"/largeFrontEnd/webFrontEnd/md/26.md\"},{\"text\":\"fs模块\",\"link\":\"/largeFrontEnd/webFrontEnd/md/27.md\"},{\"text\":\"path模块\",\"link\":\"/largeFrontEnd/webFrontEnd/md/28.md\"},{\"text\":\"HTTP模块\",\"link\":\"/largeFrontEnd/webFrontEnd/md/29.md\"},{\"text\":\"Express框架\",\"link\":\"/largeFrontEnd/webFrontEnd/md/30.md\"},{\"text\":\"Mongodb\",\"link\":\"/largeFrontEnd/webFrontEnd/md/31.md\"}]},{\"text\":\"Ajax\",\"link\":\"/largeFrontEnd/webFrontEnd/md/32.md\"},{\"text\":\"前端面试题\",\"collapsed\":true,\"items\":[{\"text\":\"一、CSS\",\"link\":\"/largeFrontEnd/webFrontEnd/md/33.md\"},{\"text\":\"二、JavaScript\",\"link\":\"/largeFrontEnd/webFrontEnd/md/34.md\"},{\"text\":\"三、HTML5CSS3\",\"link\":\"/largeFrontEnd/webFrontEnd/md/35.md\"},{\"text\":\"四、Vue\",\"link\":\"/largeFrontEnd/webFrontEnd/md/36.md\"},{\"text\":\"五、Echarts\",\"link\":\"/largeFrontEnd/webFrontEnd/md/37.md\"},{\"text\":\"六.Uni-APP\",\"link\":\"/largeFrontEnd/webFrontEnd/md/38.md\"},{\"text\":\"七、webpack\",\"link\":\"/largeFrontEnd/webFrontEnd/md/39.md\"},{\"text\":\"八、Git\",\"link\":\"/largeFrontEnd/webFrontEnd/md/40.md\"}]}],\"/largeFrontEnd/flutter/\":[{\"text\":\"Flutter入门到实战\",\"collapsed\":false,\"items\":[{\"text\":\"第一章 初识Flutter\",\"link\":\"/largeFrontEnd/flutter/chapter1/index.md\"},{\"text\":\"第二章 简介\",\"link\":\"/largeFrontEnd/flutter/chapter2/index.md\"},{\"text\":\"第三章 基础组件\",\"link\":\"/largeFrontEnd/flutter/chapter3/index.md\"},{\"text\":\"第四章 布局类组件\",\"link\":\"/largeFrontEnd/flutter/chapter4/index.md\"},{\"text\":\"第五章 容器类Widget\",\"link\":\"/largeFrontEnd/flutter/chapter5/index.md\"},{\"text\":\"第六章 可滚动组件\",\"link\":\"/largeFrontEnd/flutter/chapter6/index.md\"},{\"text\":\"第七章 功能型Widget简介\",\"link\":\"/largeFrontEnd/flutter/chapter7/index.md\"},{\"text\":\"第八章 事件处理与通知\",\"link\":\"/largeFrontEnd/flutter/chapter8/index.md\"},{\"text\":\"第九章 动画\",\"link\":\"/largeFrontEnd/flutter/chapter9/index.md\"},{\"text\":\"第十章 自定义组件\",\"link\":\"/largeFrontEnd/flutter/chapter10/index.md\"},{\"text\":\"第十一章 文件操作\",\"link\":\"/largeFrontEnd/flutter/chapter11/index.md\"},{\"text\":\"第十二章 Flutter 扩展\",\"link\":\"/largeFrontEnd/flutter/chapter12/index.md\"},{\"text\":\"第十三章 多语言\",\"link\":\"/largeFrontEnd/flutter/chapter13/index.md\"},{\"text\":\"第十四章 高级进阶\",\"link\":\"/largeFrontEnd/flutter/chapter14/index.md\"}]}]},\"socialLinks\":[{\"icon\":\"github\",\"link\":\"https://gitee.com/luoguangguang\"},{\"icon\":{\"svg\":\"<svg role=\\\"img\\\" viewBox=\\\"0 0 24 24\\\" xmlns=\\\"http://www.w3.org/2000/svg\\\"><title>Dribbble</title><path d=\\\"M12...6.38z\\\"/></svg>\"},\"link\":\"...\",\"ariaLabel\":\"cool link\"}],\"footer\":{\"message\":\"Released under the MIT License.\",\"copyright\":\"Copyright © 2023-present LG of GZU\"},\"editLink\":{\"pattern\":\"https://gitee.com/luoguangguang/note/tree/master/docs/:path\",\"text\":\"在Gitee上参与编辑此页\"},\"carbonAds\":{\"code\":\"your-carbon-code\",\"placement\":\"your-carbon-placement\"}},\"locales\":{},\"scrollOffset\":90,\"cleanUrls\":false}");</script>
    
  </body>
</html>